763 lines
30 KiB
C++
763 lines
30 KiB
C++
#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<FHoudiniGeoPartObject, TWeakObjectPtr<class UPackage> > BakedStaticMeshPackagesForParts_;
|
|
/** Transient cache of last baked materials and textures */
|
|
TMap<FString, TWeakObjectPtr<class UPackage> > BakedMaterialPackagesForIds_;
|
|
/** Cache of the temp cook content packages created by the asset for its materials/textures **/
|
|
TMap<FHoudiniGeoPartObject, TWeakObjectPtr<class UPackage> > CookedTemporaryStaticMeshPackages_;
|
|
/** Cache of the temp cook content packages created by the asset for its materials/textures **/
|
|
TMap<FString, TWeakObjectPtr<class UPackage> > 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<void ( HAPI_NodeId, FParamBlock ParamBlock )> OnComplete )
|
|
{
|
|
FFunctionGraphTask::CreateAndDispatchWhenReady( [=]() {
|
|
OnComplete( -1, FParamBlock() );
|
|
}
|
|
, TStatId(), nullptr, ENamedThreads::GameThread );
|
|
}
|
|
|
|
virtual void CookLiveAsset(
|
|
HAPI_NodeId AssetId,
|
|
const FParamBlock& ParamBlock,
|
|
PartMeshMap& CurrentParts,
|
|
TFunction<void ( bool, PartMeshMap )> 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<FAssetRegistryModule>( "AssetRegistry" );
|
|
TArray<FAssetData> AssetData;
|
|
AssetRegistryModule.Get().GetAssetsByPackageName( AssetUObjectPath, AssetData );
|
|
if( AssetData.Num() > 0 )
|
|
{
|
|
return AssetData[ 0 ].GetAsset();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
template<typename T>
|
|
T* HelperInstantiateAssetActor(
|
|
FAutomationTestBase* Test,
|
|
FName AssetPath )
|
|
{
|
|
if( UHoudiniAsset* TestAsset = Cast<UHoudiniAsset>( FindAssetUObject( AssetPath ) ) )
|
|
{
|
|
GEditor->ClickLocation = FVector::ZeroVector;
|
|
GEditor->ClickPlane = FPlane( GEditor->ClickLocation, FVector::UpVector );
|
|
|
|
TArray<AActor*> 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<T>( NewActors[ 0 ] );
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void HelperInstantiateAsset(
|
|
FAutomationTestBase* Test,
|
|
FName AssetPath,
|
|
TFunction<void( FHoudiniEngineTaskInfo, UHoudiniAsset* )> OnFinishedInstantiate )
|
|
{
|
|
UHoudiniAsset* TestAsset = Cast<UHoudiniAsset>( 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<void( bool, TMap< FHoudiniGeoPartObject, UStaticMesh * > )> 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<UTestHoudiniParameterBuilder>( 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<UHoudiniAssetParameterInt>( 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<UHoudiniAssetParameterInt>( 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<UTestHoudiniParameterBuilder>( 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<UStaticMesh>( 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<AActor*> 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<AHoudiniAssetActor>( 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<SWidget>( DetailsView->AsShared() );
|
|
if( FAutomationTestFramework::Get().IsScreenshotAllowed() )
|
|
{
|
|
const FString TestName = TEXT( "HoudiniEngineRuntimeActorTest_0.png" );
|
|
TArray<FColor> 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<UObject *> InputObjects;
|
|
TArray< FTransform > InputTransforms;
|
|
|
|
HAPI_NodeId ConnectedAssetId;
|
|
TArray< HAPI_NodeId > GeometryInputAssetIds;
|
|
|
|
UStaticMesh * GeoInput = Cast<UStaticMesh>( 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<FVector> 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
|