#include "HoudiniEngineRuntimeTest.h" #include "HoudiniApi.h" #if WITH_EDITOR #include "CoreMinimal.h" #include "Editor.h" #include "Misc/AutomationTest.h" #include "FileCacheUtilities.h" #include "StaticMeshResources.h" #include "LevelEditorViewport.h" #include "AssetRegistryModule.h" #include "PropertyEditorModule.h" #include "Tests/AutomationCommon.h" #include "IDetailsView.h" #include "HoudiniEngine.h" #include "HoudiniAsset.h" #include "HoudiniEngineUtils.h" #include "HoudiniParamUtils.h" #include "HoudiniCookHandler.h" #include "HoudiniRuntimeSettings.h" #include "HoudiniAssetActor.h" #include "HoudiniAssetComponent.h" #include "HoudiniAssetParameterInt.h" DEFINE_LOG_CATEGORY_STATIC( LogHoudiniTests, Log, All ); static constexpr int32 kTestFlags = EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter; IMPLEMENT_SIMPLE_AUTOMATION_TEST( FHoudiniEngineRuntimeMeshMarshalTest, "Houdini.Runtime.MeshMarshalTest", kTestFlags ) IMPLEMENT_SIMPLE_AUTOMATION_TEST( FHoudiniEngineRuntimeUploadStaticMeshTest, "Houdini.Runtime.UploadStaticMesh", kTestFlags ) IMPLEMENT_SIMPLE_AUTOMATION_TEST( FHoudiniEngineRuntimeActorTest, "Houdini.Runtime.ActorTest", kTestFlags ) IMPLEMENT_SIMPLE_AUTOMATION_TEST( FHoudiniEngineRuntimeParamTest, "Houdini.Runtime.ParamTest", kTestFlags ) IMPLEMENT_SIMPLE_AUTOMATION_TEST( FHoudiniEngineRuntimeBatchTest, "Houdini.Runtime.BatchTest", kTestFlags ) static float TestTickDelay = 1.0f; struct FTestCookHandler : public FHoudiniCookParams, public IHoudiniCookHandler { /** Transient cache of last baked parts */ TMap > BakedStaticMeshPackagesForParts_; /** Transient cache of last baked materials and textures */ TMap > BakedMaterialPackagesForIds_; /** Cache of the temp cook content packages created by the asset for its materials/textures **/ TMap > CookedTemporaryStaticMeshPackages_; /** Cache of the temp cook content packages created by the asset for its materials/textures **/ TMap > CookedTemporaryPackages_; FTestCookHandler( class UHoudiniAsset* InHoudiniAsset ) : FHoudiniCookParams( InHoudiniAsset ) { BakedStaticMeshPackagesForParts = &BakedStaticMeshPackagesForParts_; BakedMaterialPackagesForIds = &BakedMaterialPackagesForIds_; CookedTemporaryStaticMeshPackages = &CookedTemporaryStaticMeshPackages_; CookedTemporaryPackages = &CookedTemporaryPackages_; } virtual ~FTestCookHandler() { } virtual FString GetBakingBaseName( const struct FHoudiniGeoPartObject& GeoPartObject ) const override { if( GeoPartObject.HasCustomName() ) { return GeoPartObject.PartName; } return FString::Printf( TEXT( "test_%d_%d_%d_%d" ), GeoPartObject.ObjectId, GeoPartObject.GeoId, GeoPartObject.PartId, GeoPartObject.SplitId ); } virtual void SetStaticMeshGenerationParameters( class UStaticMesh* StaticMesh ) const 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& GeoPartObject, const FString& MaterialName ) override { return nullptr; } }; struct FHVert { int32 PointNum; float Nx, Ny, Nz; float uv0, uv1, uv2; }; struct FSortVectors { bool operator()( const FVector& A, const FVector& B ) const { if( A.X == B.X ) if( A.Y == B.Y ) if( A.Z == B.Z ) return false; else return A.Z < B.Z; else return A.Y < B.Y; else return A.X < B.X; } }; #if 0 struct FParamBlock { }; typedef TMap< FHoudiniGeoPartObject, UStaticMesh * > PartMeshMap; struct IHoudiniPluginAPI { virtual void InstatiateLiveAsset( const char* AssetPath, TFunction OnComplete ) { FFunctionGraphTask::CreateAndDispatchWhenReady( [=]() { OnComplete( -1, FParamBlock() ); } , TStatId(), nullptr, ENamedThreads::GameThread ); } virtual void CookLiveAsset( HAPI_NodeId AssetId, const FParamBlock& ParamBlock, PartMeshMap& CurrentParts, TFunction OnComplete ) { FFunctionGraphTask::CreateAndDispatchWhenReady( [=]() { PartMeshMap NewParts; OnComplete( true, NewParts ); } , TStatId(), nullptr, ENamedThreads::GameThread ); } }; #endif UWorld* HelperGetWorld() { return GWorld; } UObject* FindAssetUObject( FName AssetUObjectPath ) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( "AssetRegistry" ); TArray AssetData; AssetRegistryModule.Get().GetAssetsByPackageName( AssetUObjectPath, AssetData ); if( AssetData.Num() > 0 ) { return AssetData[ 0 ].GetAsset(); } return nullptr; } template T* HelperInstantiateAssetActor( FAutomationTestBase* Test, FName AssetPath ) { if( UHoudiniAsset* TestAsset = Cast( FindAssetUObject( AssetPath ) ) ) { GEditor->ClickLocation = FVector::ZeroVector; GEditor->ClickPlane = FPlane( GEditor->ClickLocation, FVector::UpVector ); TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( HelperGetWorld()->GetLevel( 0 ), TestAsset, true, RF_Transactional, nullptr ); Test->TestTrue( TEXT( "Placed Actor" ), NewActors.Num() > 0 ); if( NewActors.Num() > 0 ) { return Cast( NewActors[ 0 ] ); } } return nullptr; } void HelperInstantiateAsset( FAutomationTestBase* Test, FName AssetPath, TFunction OnFinishedInstantiate ) { UHoudiniAsset* TestAsset = Cast( FindAssetUObject(AssetPath) ); HAPI_AssetLibraryId AssetLibraryId = -1; TArray< HAPI_StringHandle > AssetNames; HAPI_StringHandle AssetHapiName = -1; if( FHoudiniEngineUtils::GetAssetNames( TestAsset, AssetLibraryId, AssetNames ) ) { AssetHapiName = AssetNames[ 0 ]; } auto InstGUID = FGuid::NewGuid(); FHoudiniEngineTask Task( EHoudiniEngineTaskType::AssetInstantiation, InstGUID ); Task.Asset = TestAsset; Task.ActorName = TEXT( "TestActor" ); Task.bLoadedComponent = false; Task.AssetLibraryId = AssetLibraryId; Task.AssetHapiName = AssetHapiName; FHoudiniEngine::Get().AddTask( Task ); Test->AddCommand( new FDelayedFunctionLatentCommand( [=]() { // check back on status on Instantiate FHoudiniEngineTaskInfo InstantiateTaskInfo = {}; InstantiateTaskInfo.AssetId = -1; if( FHoudiniEngine::Get().RetrieveTaskInfo( InstGUID, InstantiateTaskInfo ) ) { if( InstantiateTaskInfo.TaskState != EHoudiniEngineTaskState::FinishedInstantiation ) { Test->AddError( FString::Printf( TEXT( "AssetInstantiation failed" ) ) ); } UE_LOG( LogHoudiniTests, Log, TEXT( "InstantiateTask.StatusText: %s" ), *InstantiateTaskInfo.StatusText.ToString() ); FHoudiniEngine::Get().RemoveTaskInfo( InstGUID ); OnFinishedInstantiate( InstantiateTaskInfo, TestAsset ); } }, 1.f )); } void HelperDeleteAsset( FAutomationTestBase* Test, HAPI_NodeId AssetId ) { // Now destroy asset auto DelGUID = FGuid::NewGuid(); FHoudiniEngineTask DeleteTask( EHoudiniEngineTaskType::AssetDeletion, DelGUID ); DeleteTask.AssetId = AssetId; FHoudiniEngine::Get().AddTask( DeleteTask ); Test->AddCommand( new FDelayedFunctionLatentCommand( [=] { FHoudiniEngineTaskInfo DeleteTaskInfo; if( FHoudiniEngine::Get().RetrieveTaskInfo( DelGUID, DeleteTaskInfo ) ) { // we don't have a task state to check since it's fire and forget if( DeleteTaskInfo.Result != HAPI_RESULT_SUCCESS ) { Test->AddError( FString::Printf( TEXT( "DeleteTask.Result: %d" ), (int32)DeleteTaskInfo.Result ) ); } UE_LOG( LogHoudiniTests, Log, TEXT( "DeleteTask.StatusText: %s" ), *DeleteTaskInfo.StatusText.ToString() ); FHoudiniEngine::Get().RemoveTaskInfo( DelGUID ); } }, 1.f ) ); } void HelperCookAsset( FAutomationTestBase* Test, class UHoudiniAsset* InHoudiniAsset, HAPI_NodeId AssetId, TFunction )> OnFinishedCook ) { auto CookGUID = FGuid::NewGuid(); FHoudiniEngineTask CookTask( EHoudiniEngineTaskType::AssetCooking, CookGUID ); CookTask.AssetId = AssetId; FHoudiniEngine::Get().AddTask( CookTask ); Test->AddCommand( new FDelayedFunctionLatentCommand( [=] { FHoudiniEngineTaskInfo CookTaskInfo; if( FHoudiniEngine::Get().RetrieveTaskInfo( CookGUID, CookTaskInfo ) ) { bool CookSuccess = false; TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesOut; if( CookTaskInfo.TaskState != EHoudiniEngineTaskState::FinishedCooking ) { Test->AddError( FString::Printf( TEXT( "CookTaskInfo.Result: %d, TaskState: %d" ), (int32)CookTaskInfo.Result, (int32)CookTaskInfo.TaskState ) ); } else { // Marshal the static mesh data float GeoScale = 1.f; if( const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >() ) { GeoScale = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; } TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesIn; FTestCookHandler CookHandler ( InHoudiniAsset ); CookHandler.HoudiniCookManager = &CookHandler; CookHandler.StaticMeshBakeMode = EBakeMode::CookToTemp; FTransform AssetTransform; CookSuccess = FHoudiniEngineUtils::CreateStaticMeshesFromHoudiniAsset( AssetId, CookHandler, false, false, StaticMeshesIn, StaticMeshesOut, AssetTransform ); } OnFinishedCook( CookSuccess, StaticMeshesOut ); } }, 2.f )); } void HelperTestMeshEqual( FAutomationTestBase* Test, UStaticMesh* MeshA, UStaticMesh* MeshB ) { int32 InputNumVerts = 0; int32 InputNumTris = 0; Test->TestTrue( TEXT( "MeshA Valid" ), MeshA->RenderData->LODResources.Num() > 0 ); if( MeshA->RenderData->LODResources.Num() > 0 ) { FPositionVertexBuffer& VB = MeshA->RenderData->LODResources[ 0 ].VertexBuffers.PositionVertexBuffer; InputNumVerts = VB.GetNumVertices(); InputNumTris = MeshA->RenderData->LODResources[ 0 ].GetNumTriangles(); } Test->TestTrue( TEXT( "MeshB Valid" ), MeshB->RenderData->LODResources.Num() > 0 ); if( MeshB->RenderData->LODResources.Num() > 0 ) { Test->TestEqual( TEXT( "Num Triangles" ), InputNumTris, MeshB->RenderData->LODResources[ 0 ].GetNumTriangles() ); FPositionVertexBuffer& VB = MeshB->RenderData->LODResources[ 0 ].VertexBuffers.PositionVertexBuffer; Test->TestEqual( TEXT( "Num Verts" ), VB.GetNumVertices(), InputNumVerts ); } } bool FHoudiniEngineRuntimeParamTest::RunTest( const FString& Paramters ) { HelperInstantiateAsset( this, TEXT( "/HoudiniEngine/Test/TestParams" ), [=]( FHoudiniEngineTaskInfo InstantiateTaskInfo, UHoudiniAsset* HoudiniAsset ) { HAPI_NodeId AssetId = InstantiateTaskInfo.AssetId; if( AssetId < 0 ) return; UTestHoudiniParameterBuilder * Builder = NewObject( HelperGetWorld(), TEXT("ParmBuilder"), RF_Standalone ); bool Ok = FHoudiniParamUtils::Build( AssetId, Builder, Builder->CurrentParameters, Builder->NewParameters ); TestTrue( TEXT( "Build success" ), Ok ); if ( Ok ) { // Look at old params TestEqual( TEXT( "No Old Parms" ), Builder->CurrentParameters.Num(), 0 ); TestEqual( TEXT( "New Parms" ), Builder->NewParameters.Num(), 1 ); // Look at new params for( auto& ParamPair : Builder->NewParameters ) { UHoudiniAssetParameter* Param = ParamPair.Value; UE_LOG( LogHoudiniTests, Log, TEXT( "New Parm: %s" ), *Param->GetParameterName() ); if( UHoudiniAssetParameterInt* ParamInt = Cast( Param ) ) { // Change a parameter int32 Val = ParamInt->GetParameterValue( 0, -1 ); TestEqual( TEXT("GetParamterValue"), Val, 1 ); ParamInt->SetValue( 2, 0, false, false ); ParamInt->UploadParameterValue(); /* ADD_LATENT_AUTOMATION_COMMAND( FTakeActiveEditorScreenshotCommand( TEXT( "FHoudiniEngineRuntimeParamTest_0.png" ) )); //Wait so the screenshots have a chance to save ADD_LATENT_AUTOMATION_COMMAND( FWaitLatentCommand( 0.1f ) ); */ break; } AddError( TEXT( "Didn't find Int Param" ) ); } // Requery and verify param Builder->CurrentParameters = Builder->NewParameters; Builder->NewParameters.Empty(); Ok = FHoudiniParamUtils::Build( AssetId, Builder, Builder->CurrentParameters, Builder->NewParameters ); TestTrue( TEXT( "Build success2" ), Ok ); if( Ok ) { TestEqual( TEXT( "No Old Parms2" ), Builder->CurrentParameters.Num(), 0 ); TestEqual( TEXT( "New Parms1" ), Builder->NewParameters.Num(), 1 ); for( auto& ParamPair : Builder->NewParameters ) { UHoudiniAssetParameter* Param = ParamPair.Value; UE_LOG( LogHoudiniTests, Log, TEXT( "New Parm: %s" ), *Param->GetParameterName() ); if( UHoudiniAssetParameterInt* ParamInt = Cast( Param ) ) { int32 Val = ParamInt->GetParameterValue( 0, 0 ); TestEqual( TEXT( "GetParamterValue2" ), Val, 2 ); break; } AddError( TEXT("Didn't find Int Param2") ); } } } Builder->ConditionalBeginDestroy(); HelperCookAsset( this, HoudiniAsset, AssetId, [=]( bool CookOk, TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesOut ) { if( CookOk ) { TestEqual( TEXT( "Num Mesh" ), StaticMeshesOut.Num(), 1 ); } HelperDeleteAsset( this, AssetId ); } ); } ); return true; } bool FHoudiniEngineRuntimeBatchTest::RunTest( const FString& Paramters ) { HelperInstantiateAsset( this, TEXT( "/HoudiniEngine/Test/TestPolyReduce" ), [=]( FHoudiniEngineTaskInfo InstantiateTaskInfo, UHoudiniAsset* HoudiniAsset ) { HAPI_NodeId AssetId = InstantiateTaskInfo.AssetId; if( AssetId < 0 ) return; UTestHoudiniParameterBuilder * Builder = NewObject( HelperGetWorld(), TEXT( "ParmBuilder" ), RF_Standalone ); bool Ok = FHoudiniParamUtils::Build( AssetId, Builder, Builder->CurrentParameters, Builder->NewParameters ); TestTrue( TEXT( "Build success" ), Ok ); if( Ok ) { // Build the inputs UHoudiniAssetInput* GeoInputParm = UHoudiniAssetInput::Create( Builder, 0, AssetId ); TestTrue( TEXT( "Found Input" ), GeoInputParm != nullptr ); int32 InputNumTris = 0; UStaticMesh * GeoInput = Cast( StaticLoadObject( UObject::StaticClass(), nullptr, TEXT( "StaticMesh'/Engine/BasicShapes/Cube.Cube'" ), nullptr, LOAD_None, nullptr ) ); TestTrue( TEXT( "Load Input Mesh" ), GeoInput != nullptr ); if( ! GeoInput ) return; if( GeoInput->RenderData->LODResources.Num() > 0 ) { InputNumTris = GeoInput->RenderData->LODResources[ 0 ].GetNumTriangles(); } TestTrue( TEXT( "Find Geo Input" ), GeoInputParm != nullptr ); for( int32 Ix = 0; Ix < 3; ++Ix ) { GEditor->ClickLocation = FVector(0, Ix * 200, 0); GEditor->ClickPlane = FPlane( GEditor->ClickLocation, FVector::UpVector ); TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( HelperGetWorld()->GetLevel( 0 ), GeoInput, true, RF_Transactional, nullptr ); TestTrue( TEXT( "Placed Actor" ), NewActors.Num() > 0 ); if( GeoInputParm && NewActors.Num() > 0 ) { AActor* SMActor = NewActors.Pop(); GeoInputParm->ClearInputs(); GeoInputParm->ForceSetInputObject( SMActor, 0, true ); // triggers upload of data auto CookGUID = FGuid::NewGuid(); FHoudiniEngineTask CookTask( EHoudiniEngineTaskType::AssetCooking, CookGUID ); CookTask.AssetId = AssetId; FHoudiniEngine::Get().AddTask( CookTask ); while( true ) { FHoudiniEngineTaskInfo CookTaskInfo; if( FHoudiniEngine::Get().RetrieveTaskInfo( CookGUID, CookTaskInfo ) ) { bool CookSuccess = false; TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesOut; if( CookTaskInfo.TaskState == EHoudiniEngineTaskState::Processing ) { FPlatformProcess::Sleep( 1 ); continue; } else if( CookTaskInfo.TaskState == EHoudiniEngineTaskState::FinishedCookingWithErrors ) { AddError( FString::Printf( TEXT( "CookTaskInfo.Result: %d, TaskState: %d" ), (int32)CookTaskInfo.Result, (int32)CookTaskInfo.TaskState ) ); break; } else { // Marshal the static mesh data float GeoScale = 1.f; if( const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >() ) { GeoScale = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; } TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesIn; FTestCookHandler CookHandler ( HoudiniAsset ); CookHandler.HoudiniCookManager = &CookHandler; CookHandler.StaticMeshBakeMode = EBakeMode::CookToTemp; FTransform AssetTransform; CookSuccess = FHoudiniEngineUtils::CreateStaticMeshesFromHoudiniAsset( AssetId, CookHandler, false, false, StaticMeshesIn, StaticMeshesOut, AssetTransform ); if( CookSuccess && StaticMeshesOut.Num() > 0) { for( auto SMPair : StaticMeshesOut ) { int32 OutputNumTris = SMPair.Value->RenderData->LODResources[ 0 ].GetNumTriangles(); TestTrue( TEXT( "reduce worked" ), OutputNumTris < InputNumTris ); } } else { AddError( TEXT( "Failed to get static mesh output" ) ); } } break; } else { AddError( TEXT( "Failed to get task state" ) ); break; } } } } Builder->ConditionalBeginDestroy(); HelperDeleteAsset( this, AssetId ); } } ); return true; } bool FHoudiniEngineRuntimeActorTest::RunTest( const FString& Paramters ) { AHoudiniAssetActor* Actor = HelperInstantiateAssetActor( this, TEXT( "/HoudiniEngine/Test/InputEcho" ) ); AddCommand( new FDelayedFunctionLatentCommand( [=] { if( Actor && Actor->GetHoudiniAssetComponent() ) { UHoudiniAssetComponent* Comp = Actor->GetHoudiniAssetComponent(); TestEqual( TEXT( "Done Cooking" ), Comp->IsInstantiatingOrCooking(), false ); FPropertyEditorModule & PropertyModule = FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >( "PropertyEditor" ); // Locate the details panel. FName DetailsPanelName = "LevelEditorSelectionDetails"; TSharedPtr< IDetailsView > DetailsView = PropertyModule.FindDetailView( DetailsPanelName ); if( DetailsView.IsValid() ) { /* auto DetailsW = StaticCastSharedRef( DetailsView->AsShared() ); if( FAutomationTestFramework::Get().IsScreenshotAllowed() ) { const FString TestName = TEXT( "HoudiniEngineRuntimeActorTest_0.png" ); TArray OutImageData; FIntVector OutImageSize; if( FSlateApplication::Get().TakeScreenshot( DetailsW, OutImageData, OutImageSize ) ) { FAutomationScreenshotData Data; Data.Width = OutImageSize.X; Data.Height = OutImageSize.Y; Data.Path = TestName; FAutomationTestFramework::Get().OnScreenshotCaptured().ExecuteIfBound( OutImageData, Data ); } } */ } } }, 1.5f )); return true; } bool FHoudiniEngineRuntimeUploadStaticMeshTest::RunTest( const FString& Parameters ) { HelperInstantiateAsset( this, TEXT( "/HoudiniEngine/Test/InputEcho"), [=]( FHoudiniEngineTaskInfo InstantiateTaskInfo, UHoudiniAsset* HoudiniAsset ) { HAPI_NodeId AssetId = InstantiateTaskInfo.AssetId; if( AssetId < 0 ) { return; } TArray InputObjects; TArray< FTransform > InputTransforms; HAPI_NodeId ConnectedAssetId; TArray< HAPI_NodeId > GeometryInputAssetIds; UStaticMesh * GeoInput = Cast( StaticLoadObject( UObject::StaticClass(), nullptr, TEXT( "StaticMesh'/Engine/BasicShapes/Cube.Cube'" ), nullptr, LOAD_None, nullptr )); TestTrue( TEXT("Load Input Mesh"), GeoInput != nullptr ); if( ! GeoInput ) return; InputObjects.Add( GeoInput ); InputTransforms.Add( FTransform::Identity ); if( ! FHoudiniEngineUtils::HapiCreateInputNodeForObjects( AssetId, InputObjects, InputTransforms, ConnectedAssetId, GeometryInputAssetIds, false ) ) { AddError( FString::Printf( TEXT( "HapiCreateInputNodeForData failed" ))); } // Now connect the input int32 InputIndex = 0; HAPI_Result Result = FHoudiniApi::ConnectNodeInput( FHoudiniEngine::Get().GetSession(), AssetId, InputIndex, ConnectedAssetId, 0 ); TestEqual( TEXT("ConnectNodeInput"), HAPI_RESULT_SUCCESS, Result ); HelperCookAsset( this, HoudiniAsset, AssetId, [=]( bool Ok, TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesOut ) { if( Ok ) { TestEqual( TEXT( "Num Mesh" ), StaticMeshesOut.Num(), 1 ); } for( auto GeoPartSM : StaticMeshesOut ) { FHoudiniGeoPartObject& Part = GeoPartSM.Key; if( UStaticMesh* NewSM = GeoPartSM.Value ) { HelperTestMeshEqual( this, GeoInput, NewSM ); } break; } HelperDeleteAsset( this, AssetId ); } ); } ); return true; } bool FHoudiniEngineRuntimeMeshMarshalTest::RunTest( const FString& Parameters ) { HelperInstantiateAsset( this, TEXT( "/HoudiniEngine/Test/TestBox" ), [=]( FHoudiniEngineTaskInfo InstantiateTaskInfo, UHoudiniAsset* HoudiniAsset ) { HAPI_NodeId AssetId = InstantiateTaskInfo.AssetId; if( AssetId < 0 ) { return; } HelperCookAsset( this, HoudiniAsset, AssetId, [=]( bool Ok, TMap< FHoudiniGeoPartObject, UStaticMesh * > StaticMeshesOut ) { if( !Ok ) { return; } #define ExpectedNumVerts 24 #define ExpectedNumPoints 8 #define ExpectedNumTris 12 FHVert ExpectedVerts[ ExpectedNumVerts ] = { {1, -0.0, -0.0, -1.0, 0.333333, 0.666667, 0.0}, {5, -0.0, 0.0, -1.0, 0.333333, 0.982283, 0.0}, {4, -0.0, -0.0, -1.0, 0.64895, 0.982283, 0.0}, {0, 0.0, -0.0, -1.0, 0.64895, 0.666667, 0.0}, {2, 1.0, -0.0, -0.0, 0.0, 0.666667, 0.0}, {6, 1.0, -0.0, -0.0, 0.0, 0.982283, 0.0}, {5, 1.0, 0.0, 0.0, 0.315616, 0.982283, 0.0}, {1, 1.0, -0.0, -0.0, 0.315616, 0.666667, 0.0}, {3, -0.0, -0.0, 1.0, 0.0, 0.333333, 0.0}, {7, -0.0, -0.0, 1.0, 0.0, 0.64895, 0.0}, {6, -0.0, -0.0, 1.0, 0.315616, 0.64895, 0.0}, {2, 0.0, 0.0, 1.0, 0.315616, 0.333333, 0.0}, {0, -1.0, 0.0, -0.0, 0.333333, 0.333333, 0.0}, {4, -1.0, -0.0, -0.0, 0.333333, 0.64895, 0.0}, {7, -1.0, -0.0, 0.0, 0.64895, 0.64895, 0.0}, {3, -1.0, -0.0, -0.0, 0.64895, 0.333333, 0.0}, {2, 0.0, -1.0, -0.0, 0.64895, 0.315616, 0.0}, {1, -0.0, -1.0, -0.0, 0.64895, 0.0, 0.0}, {0, -0.0, -1.0, 0.0, 0.333333, 0.0, 0.0}, {3, -0.0, -1.0, -0.0, 0.333333, 0.315616, 0.0}, {5, -0.0, 1.0, -0.0, 0.315616, 0.315616, 0.0}, {6, -0.0, 1.0, -0.0, 0.315616, 0.0, 0.0}, {7, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0}, {4, -0.0, 1.0, -0.0, 0.0, 0.315616, 0.0}, }; FVector ExpectedPoints[ ExpectedNumPoints ] = { {-0.5, -0.5, -0.5}, {0.5, -0.5, -0.5}, {0.5, -0.5, 0.5}, {-0.5, -0.5, 0.5}, {-0.5, 0.5, -0.5}, {0.5, 0.5, -0.5}, {0.5, 0.5, 0.5}, {-0.5, 0.5, 0.5} }; // Scale our expected data into unreal space float GeoScale = 1.f; if( const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >() ) { GeoScale = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor; } for( int32 Ix = 0; Ix < ExpectedNumPoints; ++Ix ) { ExpectedPoints[ Ix ] *= GeoScale; } TestEqual( TEXT( "Num Mesh" ), StaticMeshesOut.Num(), 1 ); for( auto GeoPartSM : StaticMeshesOut ) { FHoudiniGeoPartObject& Part = GeoPartSM.Key; if( UStaticMesh* NewSM = GeoPartSM.Value ) { if( NewSM->RenderData->LODResources.Num() > 0 ) { TestEqual( TEXT( "Num Triangles" ), ExpectedNumTris, NewSM->RenderData->LODResources[ 0 ].GetNumTriangles() ); FPositionVertexBuffer& VB = NewSM->RenderData->LODResources[ 0 ].VertexBuffers.PositionVertexBuffer; const int32 VertexCount = VB.GetNumVertices(); if( VertexCount != ExpectedNumVerts ) { TestEqual( TEXT( "Num Verts" ), VertexCount, ExpectedNumVerts ); break; } TArray GeneratedPoints, ExpectedVertPositions; GeneratedPoints.SetNumUninitialized( VertexCount ); ExpectedVertPositions.SetNumUninitialized( VertexCount ); for( int32 Index = 0; Index < VertexCount; Index++ ) { GeneratedPoints[ Index ] = VB.VertexPosition( Index ); ExpectedVertPositions[ Index ] = ExpectedPoints[ ExpectedVerts[ Index ].PointNum ]; } GeneratedPoints.Sort( FSortVectors() ); ExpectedVertPositions.Sort( FSortVectors() ); for( int32 Index = 0; Index < VertexCount; Index++ ) { TestEqual( TEXT( "Points match" ), GeneratedPoints[ Index ], ExpectedVertPositions[ Index ] ); } } } break; } HelperDeleteAsset( this, AssetId ); } ); }); return true; } #endif // WITH_EDITOR