Files
Onejsky4U/Plugins/Runtime/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniEngineUtils.cpp
T

10838 lines
457 KiB
C++

/*
* Copyright (c) <2017> Side Effects Software Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "HoudiniEngineUtils.h"
#include "HoudiniApi.h"
#include "HoudiniEngineRuntimePrivatePCH.h"
#include "HoudiniRuntimeSettings.h"
#include "HoudiniAssetActor.h"
#include "HoudiniEngine.h"
#include "HoudiniAssetComponentMaterials.h"
#include "HoudiniAsset.h"
#include "HoudiniEngineString.h"
#include "HoudiniAttributeDataComponent.h"
#include "HoudiniLandscapeUtils.h"
#include "HoudiniEngineBakeUtils.h"
#include "HoudiniEngineMaterialUtils.h"
#include "Components/SplineComponent.h"
#include "LandscapeInfo.h"
#include "LandscapeComponent.h"
#include "HoudiniInstancedActorComponent.h"
#include "HoudiniMeshSplitInstancerComponent.h"
#include "CoreMinimal.h"
#include "AI/Navigation/NavCollisionBase.h"
#include "Engine/StaticMeshSocket.h"
#if WITH_EDITOR
#include "Editor.h"
#include "EditorFramework/AssetImportData.h"
#include "Interfaces/ITargetPlatform.h"
#include "Interfaces/ITargetPlatformManagerModule.h"
#include "Editor/UnrealEd/Private/GeomFitUtils.h"
#include "UnrealEd/Private/ConvexDecompTool.h"
#include "PackedNormal.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Framework/Notifications/NotificationManager.h"
#endif
#include "EngineUtils.h"
#include "PhysicsEngine/BodySetup.h"
#include "StaticMeshResources.h"
#include "Components/InstancedStaticMeshComponent.h"
#include "Components/HierarchicalInstancedStaticMeshComponent.h"
#include "Engine/SkeletalMesh.h"
#include "Rendering/SkeletalMeshModel.h"
#include "SkeletalMeshTypes.h"
#include "Misc/Paths.h"
#include "Materials/MaterialInterface.h"
#include "Materials/Material.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 <string>
#include "HAL/PlatformMisc.h"
#include "HAL/PlatformApplicationMisc.h"
#include "Internationalization/Internationalization.h"
#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE
DECLARE_CYCLE_STAT( TEXT( "Houdini: Build Static Mesh" ), STAT_BuildStaticMesh, STATGROUP_HoudiniEngine );
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"));
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::GetErrorDescription()
{
return FHoudiniEngineUtils::GetStatusString( HAPI_STATUS_CALL_RESULT, HAPI_STATUSVERBOSITY_ERRORS );
}
const FString
FHoudiniEngineUtils::GetCookState()
{
return FHoudiniEngineUtils::GetStatusString( HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS );
}
const FString
FHoudiniEngineUtils::GetCookResult()
{
return FHoudiniEngineUtils::GetStatusString( HAPI_STATUS_COOK_RESULT, HAPI_STATUSVERBOSITY_MESSAGES );
}
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< char > NodeErrorBuffer;
NodeErrorBuffer.SetNumZeroed(NodeErrorLength);
FHoudiniApi::GetComposedNodeCookResult(FHoudiniEngine::Get().GetSession(), &NodeErrorBuffer[0], NodeErrorLength);
NodeError = FString(UTF8_TO_TCHAR(&NodeErrorBuffer[0]));
}
return NodeError;
}
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::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;
}
bool
FHoudiniEngineUtils::IsLicenseHoudiniEngineIndie()
{
HAPI_License LicenseTypeValue = HAPI_LICENSE_NONE;
if ( FHoudiniApi::GetSessionEnvInt( FHoudiniEngine::Get().GetSession(),
HAPI_SESSIONENVINT_LICENSE, (int32 *) &LicenseTypeValue ) == HAPI_RESULT_SUCCESS )
{
return HAPI_LICENSE_HOUDINI_ENGINE_INDIE == LicenseTypeValue;
}
return false;
}
bool
FHoudiniEngineUtils::ComputeAssetPresetBufferLength( HAPI_NodeId AssetId, int32 & OutBufferLength )
{
HAPI_AssetInfo AssetInfo;
FHoudiniApi::AssetInfo_Init(&AssetInfo);
OutBufferLength = 0;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAssetInfo(
FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ), false );
int32 BufferLength = 0;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPresetBufLength(
FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId,
HAPI_PRESETTYPE_BINARY, NULL, &BufferLength ), false );
OutBufferLength = BufferLength;
return true;
}
bool
FHoudiniEngineUtils::SetAssetPreset( HAPI_NodeId AssetId, const TArray< char > & PresetBuffer )
{
if ( PresetBuffer.Num() > 0 )
{
HAPI_AssetInfo AssetInfo;
FHoudiniApi::AssetInfo_Init(&AssetInfo);
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAssetInfo(
FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetPreset(
FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId,
HAPI_PRESETTYPE_BINARY, NULL, &PresetBuffer[ 0 ],
PresetBuffer.Num() ), false );
return true;
}
return false;
}
bool
FHoudiniEngineUtils::GetAssetPreset( HAPI_NodeId AssetId, TArray< char > & PresetBuffer )
{
PresetBuffer.Empty();
HAPI_NodeId NodeId;
HAPI_AssetInfo AssetInfo;
FHoudiniApi::AssetInfo_Init(&AssetInfo);
if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo(
FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo))
{
NodeId = AssetInfo.nodeId;
}
else
NodeId = AssetId;
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::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::DestroyHoudiniAsset( HAPI_NodeId AssetId )
{
return FHoudiniApi::DeleteNode( FHoudiniEngine::Get().GetSession(), AssetId ) == HAPI_RESULT_SUCCESS;
}
void
FHoudiniEngineUtils::ConvertUnrealString( const FString & UnrealString, std::string & String )
{
String = TCHAR_TO_UTF8( *UnrealString );
}
void
FHoudiniEngineUtils::TranslateHapiTransform( const HAPI_Transform & HapiTransform, FTransform & UnrealTransform )
{
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
float TransformScaleFactor = HAPI_UNREAL_SCALE_FACTOR_TRANSLATION;
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
if ( HoudiniRuntimeSettings )
{
TransformScaleFactor = HoudiniRuntimeSettings->TransformScaleFactor;
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
}
if ( ImportAxis == HRSAI_Unreal )
{
FQuat ObjectRotation(
HapiTransform.rotationQuaternion[ 0 ], HapiTransform.rotationQuaternion[ 1 ],
HapiTransform.rotationQuaternion[ 2 ], -HapiTransform.rotationQuaternion[ 3 ]);
Swap( ObjectRotation.Y, ObjectRotation.Z );
FVector ObjectTranslation( HapiTransform.position[ 0 ], HapiTransform.position[ 1 ], HapiTransform.position[ 2 ] );
ObjectTranslation *= TransformScaleFactor;
Swap( ObjectTranslation[ 2 ], ObjectTranslation[ 1 ] );
FVector ObjectScale3D( HapiTransform.scale[ 0 ], HapiTransform.scale[ 1 ], HapiTransform.scale[ 2 ] );
Swap( ObjectScale3D.Y, ObjectScale3D.Z );
UnrealTransform.SetComponents( ObjectRotation, ObjectTranslation, ObjectScale3D );
}
else if ( ImportAxis == HRSAI_Houdini )
{
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 *= TransformScaleFactor;
FVector ObjectScale3D( HapiTransform.scale[ 0 ], HapiTransform.scale[ 1 ], HapiTransform.scale[ 2 ] );
UnrealTransform.SetComponents( ObjectRotation, ObjectTranslation, ObjectScale3D );
}
else
{
// Not valid enum value.
check( 0 );
}
}
void
FHoudiniEngineUtils::TranslateHapiTransform( const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform )
{
float HapiMatrix[ 16 ];
FHoudiniApi::ConvertTransformEulerToMatrix( FHoudiniEngine::Get().GetSession(), &HapiTransformEuler, HapiMatrix );
HAPI_Transform HapiTransformQuat;
FHoudiniApi::Transform_Init(&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 )
{
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
float TransformScaleFactor = HAPI_UNREAL_SCALE_FACTOR_TRANSLATION;
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
if ( HoudiniRuntimeSettings )
{
TransformScaleFactor = HoudiniRuntimeSettings->TransformScaleFactor;
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
}
//FMemory::Memzero< HAPI_Transform >( HapiTransform );
FHoudiniApi::Transform_Init(&HapiTransform);
HapiTransform.rstOrder = HAPI_SRT;
FQuat UnrealRotation = UnrealTransform.GetRotation();
FVector UnrealTranslation = UnrealTransform.GetTranslation();
FVector UnrealScale = UnrealTransform.GetScale3D();
if ( ImportAxis == HRSAI_Unreal )
{
Swap( UnrealRotation.Y, UnrealRotation.Z );
HapiTransform.rotationQuaternion[ 0 ] = -UnrealRotation.X;
HapiTransform.rotationQuaternion[ 1 ] = -UnrealRotation.Y;
HapiTransform.rotationQuaternion[ 2 ] = -UnrealRotation.Z;
HapiTransform.rotationQuaternion[ 3 ] = UnrealRotation.W;
UnrealTranslation /= TransformScaleFactor;
Swap( UnrealTranslation.Y, UnrealTranslation.Z );
HapiTransform.position[ 0 ] = UnrealTranslation.X;
HapiTransform.position[ 1 ] = UnrealTranslation.Y;
HapiTransform.position[ 2 ] = UnrealTranslation.Z;
Swap( UnrealScale.Y, UnrealScale.Z );
HapiTransform.scale[ 0 ] = UnrealScale.X;
HapiTransform.scale[ 1 ] = UnrealScale.Y;
HapiTransform.scale[ 2 ] = UnrealScale.Z;
}
else if ( ImportAxis == HRSAI_Houdini )
{
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;
}
else
{
// Not valid enum value.
check( 0 );
}
}
void
FHoudiniEngineUtils::TranslateUnrealTransform(
const FTransform & UnrealTransform,
HAPI_TransformEuler & HapiTransformEuler )
{
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
float TransformScaleFactor = HAPI_UNREAL_SCALE_FACTOR_TRANSLATION;
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
if ( HoudiniRuntimeSettings )
{
TransformScaleFactor = HoudiniRuntimeSettings->TransformScaleFactor;
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
}
//FMemory::Memzero< HAPI_TransformEuler >( HapiTransformEuler );
FHoudiniApi::TransformEuler_Init(&HapiTransformEuler);
HapiTransformEuler.rstOrder = HAPI_SRT;
HapiTransformEuler.rotationOrder = HAPI_XYZ;
FQuat UnrealRotation = UnrealTransform.GetRotation();
FVector UnrealTranslation = UnrealTransform.GetTranslation();
UnrealTranslation /= TransformScaleFactor;
FVector UnrealScale = UnrealTransform.GetScale3D();
if ( ImportAxis == HRSAI_Unreal )
{
// switch quat to Y-up, LHR
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( UnrealTranslation.Y, UnrealTranslation.Z );
HapiTransformEuler.position[ 0 ] = UnrealTranslation.X;
HapiTransformEuler.position[ 1 ] = UnrealTranslation.Y;
HapiTransformEuler.position[ 2 ] = UnrealTranslation.Z;
Swap( UnrealScale.Y, UnrealScale.Z );
HapiTransformEuler.scale[ 0 ] = UnrealScale.X;
HapiTransformEuler.scale[ 1 ] = UnrealScale.Y;
HapiTransformEuler.scale[ 2 ] = UnrealScale.Z;
}
else if ( ImportAxis == HRSAI_Houdini )
{
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;
}
else
{
// Not valid enum value.
check( 0 );
}
}
bool
FHoudiniEngineUtils::SetCurrentTime( float CurrentTime )
{
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetTime(
FHoudiniEngine::Get().GetSession(), CurrentTime ), false );
return true;
}
bool
FHoudiniEngineUtils::GetHoudiniAssetName( HAPI_NodeId AssetId, FString & NameString )
{
HAPI_AssetInfo AssetInfo;
FHoudiniApi::AssetInfo_Init(&AssetInfo);
if ( FHoudiniApi::GetAssetInfo( FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ) == HAPI_RESULT_SUCCESS )
{
FHoudiniEngineString HoudiniEngineString( AssetInfo.nameSH );
return HoudiniEngineString.ToFString( NameString );
}
return false;
}
int32
FHoudiniEngineUtils::HapiGetGroupCountByType( HAPI_GroupType GroupType, HAPI_GeoInfo & GeoInfo )
{
switch ( GroupType )
{
case HAPI_GROUPTYPE_POINT: return GeoInfo.pointGroupCount;
case HAPI_GROUPTYPE_PRIM: return GeoInfo.primitiveGroupCount;
default: break;
}
return 0;
}
int32
FHoudiniEngineUtils::HapiGetElementCountByGroupType( HAPI_GroupType GroupType, HAPI_PartInfo & PartInfo )
{
switch ( GroupType )
{
case HAPI_GROUPTYPE_POINT: return PartInfo.pointCount;
case HAPI_GROUPTYPE_PRIM: return PartInfo.faceCount;
default: break;
}
return 0;
}
bool
FHoudiniEngineUtils::HapiGetGroupNames(
HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, HAPI_PartId PartId,
HAPI_GroupType GroupType, TArray< FString > & GroupNames, const bool& isPackedPrim )
{
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);
GroupCount = FHoudiniEngineUtils::HapiGetGroupCountByType(GroupType, GeoInfo);
}
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
GroupCount = PrimGroupCount;
}
if ( GroupCount <= 0 )
return true;
std::vector< int32 > GroupNameHandles( GroupCount, 0 );
if ( !isPackedPrim )
{
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetGroupNames(
FHoudiniEngine::Get().GetSession(),
GeoId, GroupType, &GroupNameHandles[ 0 ], GroupCount ), false );
}
else
{
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetGroupNamesOnPackedInstancePart(
FHoudiniEngine::Get().GetSession(),
GeoId, PartId, GroupType, &GroupNameHandles[ 0 ], GroupCount ), false );
}
for ( int32 NameIdx = 0; NameIdx < GroupCount; ++NameIdx )
{
FString GroupName = TEXT( "" );
FHoudiniEngineString HoudiniEngineString( GroupNameHandles[ NameIdx ] );
HoudiniEngineString.ToFString( GroupName );
GroupNames.Add( GroupName );
}
return true;
}
bool
FHoudiniEngineUtils::HapiGetGroupMembership(
HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, HAPI_PartId PartId,
HAPI_GroupType GroupType, const FString & GroupName, TArray< int32 > & GroupMembership )
{
HAPI_PartInfo PartInfo;
FHoudiniApi::PartInfo_Init(&PartInfo);
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPartInfo(
FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PartInfo ), false );
int32 ElementCount = FHoudiniEngineUtils::HapiGetElementCountByGroupType( GroupType, PartInfo );
std::string ConvertedGroupName = TCHAR_TO_UTF8( *GroupName );
if ( ElementCount < 1 )
return false;
GroupMembership.SetNumUninitialized( ElementCount );
if ( !PartInfo.isInstanced )
{
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetGroupMembership(
FHoudiniEngine::Get().GetSession(), GeoId, PartId, GroupType,
ConvertedGroupName.c_str(), NULL, &GroupMembership[ 0 ], 0, ElementCount ), false );
}
else
{
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetGroupMembershipOnPackedInstancePart(
FHoudiniEngine::Get().GetSession(), GeoId, PartId, GroupType,
ConvertedGroupName.c_str(), NULL, &GroupMembership[ 0 ], 0, ElementCount ), false );
}
return true;
}
bool
FHoudiniEngineUtils::HapiCheckGroupMembership(
const FHoudiniGeoPartObject & HoudiniGeoPartObject, HAPI_GroupType GroupType, const FString & GroupName )
{
return FHoudiniEngineUtils::HapiCheckGroupMembership(
HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId, HoudiniGeoPartObject.GeoId,
HoudiniGeoPartObject.PartId, GroupType, GroupName );
}
bool
FHoudiniEngineUtils::HapiCheckGroupMembership(
HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, HAPI_PartId PartId,
HAPI_GroupType GroupType, const FString & GroupName )
{
TArray< int32 > GroupMembership;
if ( FHoudiniEngineUtils::HapiGetGroupMembership( AssetId, ObjectId, GeoId, PartId, GroupType, GroupName, GroupMembership ) )
{
int32 GroupSum = 0;
for ( int32 Idx = 0; Idx < GroupMembership.Num(); ++Idx )
GroupSum += GroupMembership[ Idx ];
return GroupSum > 0;
}
return false;
}
void
FHoudiniEngineUtils::HapiRetrieveParameterNames(
const TArray< HAPI_ParmInfo > & ParmInfos,
TArray< std::string > & Names)
{
static const std::string InvalidParameterName( "Invalid Parameter Name" );
Names.Empty();
for ( int32 ParmIdx = 0; ParmIdx < ParmInfos.Num(); ++ParmIdx )
{
const HAPI_ParmInfo& NodeParmInfo = ParmInfos[ ParmIdx ];
HAPI_StringHandle NodeParmHandle = NodeParmInfo.nameSH;
int32 NodeParmNameLength = 0;
FHoudiniApi::GetStringBufLength( FHoudiniEngine::Get().GetSession(), NodeParmHandle, &NodeParmNameLength );
if ( NodeParmNameLength )
{
std::vector< char > NodeParmName( NodeParmNameLength, '\0' );
HAPI_Result Result = FHoudiniApi::GetString(
FHoudiniEngine::Get().GetSession(), NodeParmHandle,
&NodeParmName[ 0 ], NodeParmNameLength );
if ( Result == HAPI_RESULT_SUCCESS )
Names.Add( std::string( NodeParmName.begin(), NodeParmName.end() - 1 ) );
else
Names.Add( InvalidParameterName );
}
else
{
Names.Add( InvalidParameterName );
}
}
}
void
FHoudiniEngineUtils::HapiRetrieveParameterNames( const TArray< HAPI_ParmInfo > & ParmInfos, TArray< FString > & Names )
{
TArray< std::string > IntermediateNames;
FHoudiniEngineUtils::HapiRetrieveParameterNames( ParmInfos, IntermediateNames );
for ( int32 Idx = 0, Num = IntermediateNames.Num(); Idx < Num; ++Idx )
Names.Add( UTF8_TO_TCHAR( IntermediateNames[ Idx ].c_str() ) );
}
bool
FHoudiniEngineUtils::HapiCheckAttributeExists(
HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId,
HAPI_PartId PartId, const char * Name)
{
for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx)
{
if (HapiCheckAttributeExists(AssetId, ObjectId, GeoId,
PartId, Name, (HAPI_AttributeOwner)AttrIdx))
return true;
}
return false;
}
bool
FHoudiniEngineUtils::HapiCheckAttributeExists(
HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId,
HAPI_PartId PartId, const char * Name, HAPI_AttributeOwner Owner )
{
HAPI_AttributeInfo AttribInfo;
FHoudiniApi::AttributeInfo_Init(&AttribInfo);
//FMemory::Memzero< HAPI_AttributeInfo >( AttribInfo );
if ( FHoudiniApi::GetAttributeInfo(
FHoudiniEngine::Get().GetSession(),
GeoId, PartId, Name, Owner, &AttribInfo ) != HAPI_RESULT_SUCCESS )
{
return false;
}
return AttribInfo.exists;
}
bool
FHoudiniEngineUtils::HapiCheckAttributeExists(
const FHoudiniGeoPartObject & HoudiniGeoPartObject, const char * Name,
HAPI_AttributeOwner Owner )
{
if ( Owner == HAPI_ATTROWNER_INVALID )
{
return FHoudiniEngineUtils::HapiCheckAttributeExists(
HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId,
HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, Name );
}
else
{
return FHoudiniEngineUtils::HapiCheckAttributeExists(
HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId,
HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, Name, Owner);
}
}
bool
FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId,
HAPI_PartId PartId, const char * Name, HAPI_AttributeInfo & ResultAttributeInfo,
TArray< float > & Data, int32 TupleSize, HAPI_AttributeOwner Owner )
{
ResultAttributeInfo.exists = false;
// Reset container size.
Data.SetNumUninitialized( 0 );
int32 OriginalTupleSize = TupleSize;
HAPI_AttributeInfo AttributeInfo;
FHoudiniApi::AttributeInfo_Init(&AttributeInfo);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfo );
if ( Owner == HAPI_ATTROWNER_INVALID )
{
for ( int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx )
{
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo(
FHoudiniEngine::Get().GetSession(), GeoId, PartId, Name,
(HAPI_AttributeOwner) AttrIdx, &AttributeInfo ), false );
if ( AttributeInfo.exists )
break;
}
}
else
{
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo(
FHoudiniEngine::Get().GetSession(), GeoId, PartId, Name,
Owner, &AttributeInfo ), false );
}
if ( !AttributeInfo.exists )
return false;
if ( OriginalTupleSize > 0 )
AttributeInfo.tupleSize = OriginalTupleSize;
// Allocate sufficient buffer for data.
Data.SetNumUninitialized( AttributeInfo.count * AttributeInfo.tupleSize );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeFloatData(
FHoudiniEngine::Get().GetSession(), GeoId, PartId, Name,
&AttributeInfo, -1, &Data[ 0 ], 0, AttributeInfo.count ), false );
// Store the retrieved attribute information.
ResultAttributeInfo = AttributeInfo;
return true;
}
bool
FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
const FHoudiniGeoPartObject & HoudiniGeoPartObject, const char * Name,
HAPI_AttributeInfo & ResultAttributeInfo, TArray< float > & Data, int32 TupleSize, HAPI_AttributeOwner Owner )
{
return FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId,
HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, Name,
ResultAttributeInfo, Data, TupleSize, Owner );
}
bool
FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(
HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId,
HAPI_PartId PartId, const char * Name, HAPI_AttributeInfo & ResultAttributeInfo,
TArray< int32 > & Data, int32 TupleSize, HAPI_AttributeOwner Owner )
{
ResultAttributeInfo.exists = false;
// Reset container size.
Data.SetNumUninitialized( 0 );
int32 OriginalTupleSize = TupleSize;
HAPI_AttributeInfo AttributeInfo;
FHoudiniApi::AttributeInfo_Init(&AttributeInfo);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfo );
if ( Owner == HAPI_ATTROWNER_INVALID )
{
for ( int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx )
{
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo(
FHoudiniEngine::Get().GetSession(),
GeoId, PartId, Name, (HAPI_AttributeOwner) AttrIdx, &AttributeInfo ), false );
if ( AttributeInfo.exists )
break;
}
}
else
{
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo(
FHoudiniEngine::Get().GetSession(),
GeoId, PartId, Name, Owner, &AttributeInfo ), false );
}
if ( !AttributeInfo.exists )
return false;
if ( OriginalTupleSize > 0 )
AttributeInfo.tupleSize = OriginalTupleSize;
// Allocate sufficient buffer for data.
Data.SetNumUninitialized( AttributeInfo.count * AttributeInfo.tupleSize );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeIntData(
FHoudiniEngine::Get().GetSession(),
GeoId, PartId, Name, &AttributeInfo, -1, &Data[ 0 ], 0, AttributeInfo.count ), false );
// Store the retrieved attribute information.
ResultAttributeInfo = AttributeInfo;
return true;
}
bool
FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(
const FHoudiniGeoPartObject & HoudiniGeoPartObject,
const char * Name, HAPI_AttributeInfo & ResultAttributeInfo,
TArray< int32 > & Data, int32 TupleSize, HAPI_AttributeOwner Owner )
{
return FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(
HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId,
HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, Name,
ResultAttributeInfo, Data, TupleSize, Owner );
}
bool
FHoudiniEngineUtils::HapiGetAttributeDataAsString(
HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId,
HAPI_PartId PartId, const char * Name, HAPI_AttributeInfo & ResultAttributeInfo,
TArray< FString > & Data, int32 TupleSize, HAPI_AttributeOwner Owner )
{
ResultAttributeInfo.exists = false;
// Reset container size.
Data.Empty();
int32 OriginalTupleSize = TupleSize;
HAPI_AttributeInfo AttributeInfo;
FHoudiniApi::AttributeInfo_Init(&AttributeInfo);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfo );
if ( Owner == HAPI_ATTROWNER_INVALID )
{
for ( int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx )
{
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo(
FHoudiniEngine::Get().GetSession(),
GeoId, PartId, Name, (HAPI_AttributeOwner) AttrIdx, &AttributeInfo ), false );
if ( AttributeInfo.exists )
break;
}
}
else
{
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo(
FHoudiniEngine::Get().GetSession(),
GeoId, PartId, Name, Owner, &AttributeInfo ), false );
}
if ( !AttributeInfo.exists )
return false;
if ( OriginalTupleSize > 0 )
AttributeInfo.tupleSize = OriginalTupleSize;
TArray< HAPI_StringHandle > StringHandles;
StringHandles.Init( -1, AttributeInfo.count * AttributeInfo.tupleSize );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeStringData(
FHoudiniEngine::Get().GetSession(), GeoId, PartId, Name, &AttributeInfo,
&StringHandles[ 0 ], 0, AttributeInfo.count ), false );
// Set the output data size
Data.SetNum(AttributeInfo.count);
// Use a map to minimize the number of HAPI calls for performance!
TMap<int32, FString> StringHandleToStringMap;
for ( int32 Idx = 0; Idx < StringHandles.Num(); ++Idx )
{
const HAPI_StringHandle& CurrentSH = StringHandles[Idx];
FString* FoundString = StringHandleToStringMap.Find(CurrentSH);
if ( FoundString )
{
Data[Idx] = *FoundString;
}
else
{
FString HapiString = TEXT("");
FHoudiniEngineString HoudiniEngineString(CurrentSH);
HoudiniEngineString.ToFString(HapiString);
StringHandleToStringMap.Add(CurrentSH, HapiString);
Data[Idx] = HapiString;
}
}
// Store the retrieved attribute information.
ResultAttributeInfo = AttributeInfo;
return true;
}
bool
FHoudiniEngineUtils::HapiGetAttributeDataAsString(
const FHoudiniGeoPartObject & HoudiniGeoPartObject, const char * Name,
HAPI_AttributeInfo & ResultAttributeInfo, TArray< FString > & Data, int32 TupleSize, HAPI_AttributeOwner Owner )
{
return FHoudiniEngineUtils::HapiGetAttributeDataAsString(
HoudiniGeoPartObject.AssetId,
HoudiniGeoPartObject.ObjectId,
HoudiniGeoPartObject.GeoId,
HoudiniGeoPartObject.PartId, Name,
ResultAttributeInfo, Data, TupleSize, Owner );
}
bool
FHoudiniEngineUtils::HapiGetInstanceTransforms(
HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId,
HAPI_PartId PartId, TArray< FTransform > & Transforms )
{
Transforms.Empty();
// Number of instances is number of points.
HAPI_PartInfo PartInfo;
FHoudiniApi::PartInfo_Init(&PartInfo);
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPartInfo(
FHoudiniEngine::Get().GetSession(), GeoId,
PartId, &PartInfo ), false );
if ( PartInfo.pointCount == 0 )
return false;
TArray< HAPI_Transform > InstanceTransforms;
InstanceTransforms.SetNumUninitialized( PartInfo.pointCount );
for (int32 Idx = 0; Idx < InstanceTransforms.Num(); Idx++)
FHoudiniApi::Transform_Init(&(InstanceTransforms[Idx]));
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetInstanceTransformsOnPart(
FHoudiniEngine::Get().GetSession(),
GeoId, PartId, HAPI_SRT, &InstanceTransforms[ 0 ], 0, PartInfo.pointCount ), false );
for ( int32 Idx = 0; Idx < PartInfo.pointCount; ++Idx )
{
const HAPI_Transform& HapiInstanceTransform = InstanceTransforms[ Idx ];
FTransform TransformMatrix;
FHoudiniEngineUtils::TranslateHapiTransform( HapiInstanceTransform, TransformMatrix );
Transforms.Add( TransformMatrix );
}
return true;
}
bool
FHoudiniEngineUtils::HapiGetInstanceTransforms(
const FHoudiniGeoPartObject & HoudiniGeoPartObject,
TArray< FTransform > & Transforms )
{
return FHoudiniEngineUtils::HapiGetInstanceTransforms(
HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId,
HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, Transforms );
}
FColor
FHoudiniEngineUtils::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;
}
#if WITH_EDITOR
void
FHoudiniEngineUtils::ResetRawMesh( FRawMesh & RawMesh )
{
// Unlike Empty this will not change memory allocations.
RawMesh.FaceMaterialIndices.Reset();
RawMesh.FaceSmoothingMasks.Reset();
RawMesh.VertexPositions.Reset();
RawMesh.WedgeIndices.Reset();
RawMesh.WedgeTangentX.Reset();
RawMesh.WedgeTangentY.Reset();
RawMesh.WedgeTangentZ.Reset();
RawMesh.WedgeColors.Reset();
for ( int32 Idx = 0; Idx < MAX_MESH_TEXTURE_COORDS; ++Idx )
RawMesh.WedgeTexCoords[ Idx ].Reset();
}
#endif
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;
}
bool
FHoudiniEngineUtils::HapiGetParameterDataAsFloat(
HAPI_NodeId NodeId, const std::string ParmName, float DefaultValue, float & OutValue )
{
float Value = DefaultValue;
bool bComputed = false;
HAPI_ParmInfo FoundParamInfo;
FHoudiniApi::ParmInfo_Init(&FoundParamInfo);
HAPI_ParmId ParmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeId, ParmName, FoundParamInfo );
if ( ParmId > -1 )
{
if ( FHoudiniApi::GetParmFloatValues(
FHoudiniEngine::Get().GetSession(), NodeId, &Value,
FoundParamInfo.floatValuesIndex, 1 ) == HAPI_RESULT_SUCCESS )
{
bComputed = true;
}
}
OutValue = Value;
return bComputed;
}
bool
FHoudiniEngineUtils::HapiGetParameterDataAsInteger(
HAPI_NodeId NodeId, const std::string ParmName, int32 DefaultValue, int32 & OutValue)
{
int32 Value = DefaultValue;
bool bComputed = false;
HAPI_ParmInfo FoundParamInfo;
FHoudiniApi::ParmInfo_Init(&FoundParamInfo);
HAPI_ParmId ParmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeId, ParmName, FoundParamInfo );
if ( ParmId > -1 )
{
if ( FHoudiniApi::GetParmIntValues(
FHoudiniEngine::Get().GetSession(), NodeId, &Value,
FoundParamInfo.intValuesIndex, 1 ) == HAPI_RESULT_SUCCESS )
{
bComputed = true;
}
}
OutValue = Value;
return bComputed;
}
bool
FHoudiniEngineUtils::HapiGetParameterDataAsString(
HAPI_NodeId NodeId, const std::string ParmName,
const FString & DefaultValue, FString & OutValue )
{
FString Value;
bool bComputed = false;
HAPI_ParmInfo FoundParamInfo;
FHoudiniApi::ParmInfo_Init(&FoundParamInfo);
HAPI_ParmId ParmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag( NodeId, ParmName, FoundParamInfo );
if ( ParmId > -1 )
{
HAPI_StringHandle StringHandle;
if ( FHoudiniApi::GetParmStringValues(
FHoudiniEngine::Get().GetSession(), NodeId, false,
&StringHandle, FoundParamInfo.stringValuesIndex, 1 ) == HAPI_RESULT_SUCCESS )
{
FHoudiniEngineString HoudiniEngineString( StringHandle );
if ( HoudiniEngineString.ToFString( Value ) )
bComputed = true;
}
}
if ( bComputed )
OutValue = Value;
else
OutValue = DefaultValue;
return bComputed;
}
bool
FHoudiniEngineUtils::HapiGetParameterUnit( const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, FString& OutUnitString )
{
//
OutUnitString = TEXT("");
// We're looking for the parameter unit tag
FString UnitTag = "units";
bool HasUnit = false;
// Does the parameter has the "units" tag?
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ParmHasTag(
FHoudiniEngine::Get().GetSession(), NodeId, ParmId,
TCHAR_TO_ANSI(*UnitTag), &HasUnit ), false );
if ( !HasUnit )
return false;
// Get the unit string value
HAPI_StringHandle StringHandle;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmTagValue(
FHoudiniEngine::Get().GetSession(), NodeId, ParmId,
TCHAR_TO_ANSI(*UnitTag), &StringHandle ), false );
// Get the actual string value.
FString UnitString = TEXT("");
FHoudiniEngineString HoudiniEngineString( StringHandle );
if ( HoudiniEngineString.ToFString( UnitString ) )
{
// 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;
}
return false;
}
bool
FHoudiniEngineUtils::HapiGetParameterNoSwapTag( const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, bool& NoSwapValue )
{
// Default noswap to false
NoSwapValue = false;
// We're looking for the parameter noswap tag
FString NoSwapTag = "hengine_noswap";
// Does the parameter has the "hengine_noswap" tag?
bool HasNoSwap = false;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ParmHasTag(
FHoudiniEngine::Get().GetSession(), NodeId, ParmId,
TCHAR_TO_ANSI( *NoSwapTag ), &HasNoSwap ), false );
if ( !HasNoSwap )
return true;
// Set NoSwap to true
NoSwapValue = true;
return true;
}
bool
FHoudiniEngineUtils::HapiGetParameterTag( 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
FHoudiniEngineUtils::IsValidNodeId( HAPI_NodeId NodeId )
{
return NodeId != -1;
}
bool
FHoudiniEngineUtils::HapiCreateCurveNode( HAPI_NodeId & ConnectedAssetId )
{
#if WITH_EDITOR
// Create the curve SOP Node
HAPI_NodeId NodeId = -1;
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), -1,
"SOP/curve", nullptr, false, &NodeId), false);
ConnectedAssetId = NodeId;
// Submit default points to curve.
HAPI_ParmId ParmId = -1;
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName(
FHoudiniEngine::Get().GetSession(), NodeId,
HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId), false);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue(
FHoudiniEngine::Get().GetSession(), NodeId,
HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT, ParmId, 0), false);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode(
FHoudiniEngine::Get().GetSession(), NodeId, nullptr), false);
#endif // WITH_EDITOR
return true;
}
bool
FHoudiniEngineUtils::HapiCreateCurveInputNodeForData(
HAPI_NodeId HostAssetId,
HAPI_NodeId& ConnectedAssetId,
TArray<FVector>* Positions,
TArray<FQuat>* Rotations /*= nullptr*/,
TArray<FVector>* Scales3d /*= nullptr*/,
TArray<float>* UniformScales /*= nullptr*/,
bool ForceClose /*=false*/)
{
#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 ) || !FHoudiniEngineUtils::IsHoudiniNodeValid( HostAssetId ) )
return false;
// Check if connected asset id is valid, if it is not, we need to create an input asset.
if (ConnectedAssetId < 0)
{
HAPI_NodeId NodeId = -1;
// Create the curve SOP Node
if (!HapiCreateCurveNode(NodeId))
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.
ConnectedAssetId = 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(), ConnectedAssetId);
}
//
// 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)
//
// Reading the curve parameters
int32 CurveTypeValue, CurveMethodValue, CurveClosed;
FHoudiniEngineUtils::HapiGetParameterDataAsInteger(
ConnectedAssetId, HAPI_UNREAL_PARAM_CURVE_TYPE,
0, CurveTypeValue);
FHoudiniEngineUtils::HapiGetParameterDataAsInteger(
ConnectedAssetId, HAPI_UNREAL_PARAM_CURVE_METHOD,
0, CurveMethodValue);
FHoudiniEngineUtils::HapiGetParameterDataAsInteger(
ConnectedAssetId, HAPI_UNREAL_PARAM_CURVE_CLOSED,
1, CurveClosed);
if ( ForceClose )
{
// We need to update the closed parameter
FHoudiniApi::SetParmIntValue(
FHoudiniEngine::Get().GetSession(), ConnectedAssetId,
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(), ConnectedAssetId,
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("");
FHoudiniEngineUtils::CreatePositionsString(*Positions, PositionString);
// Get param id for the PositionString and modify it
HAPI_ParmId ParmId = -1;
if (FHoudiniApi::GetParmIdFromName(
FHoudiniEngine::Get().GetSession(), ConnectedAssetId,
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(), ConnectedAssetId,
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);
bool bAddUniformScales = (UniformScales != nullptr);
if (!bAddRotations && !bAddScales3d && !bAddUniformScales)
{
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode(
FHoudiniEngine::Get().GetSession(), ConnectedAssetId, nullptr), false);
return true;
}
// Setting up the first cook, without the curve refinement
HAPI_CookOptions CookOptions;
FHoudiniApi::CookOptions_Init(&CookOptions);
//FMemory::Memzero< HAPI_CookOptions >(CookOptions);
CookOptions.curveRefineLOD = 8.0f;
CookOptions.clearErrorsAndWarnings = false;
CookOptions.maxVerticesPerPrimitive = -1;
CookOptions.splitGeosByGroup = false;
CookOptions.splitGeosByAttribute = false;
CookOptions.splitAttrSH = 0;
CookOptions.handleBoxPartTypes = false;
CookOptions.handleSpherePartTypes = false;
CookOptions.packedPrimInstancingMode = HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT;
CookOptions.refineCurveToLinear = false;
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode(
FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &CookOptions), false);
// We can now read back the Part infos from the cooked curve.
HAPI_PartInfo PartInfos;
FHoudiniApi::PartInfo_Init(&PartInfos);
//FMemory::Memzero< HAPI_PartInfo >(PartInfos);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo(
FHoudiniEngine::Get().GetSession(), ConnectedAssetId, 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);
}
if ( UniformScales && UniformScales->IsValidIndex(nIndex1) && UniformScales->IsValidIndex(nIndex2) )
{
float interpolation = fCoeff * (*UniformScales)[nIndex1] + (1.0f - fCoeff) * (*UniformScales)[nIndex2];
if (UniformScales->IsValidIndex(nInsertIndex))
UniformScales->Insert(interpolation, nInsertIndex);
else
UniformScales->Add(interpolation);
}
};
// Lambda function that duplicates rotation, scale and uniform scales values
// at nIndex and insert/adds it at nInsertIndex
auto DuplicateRotScaleUScale = [&](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);
}
if ( UniformScales && UniformScales->IsValidIndex(nIndex) )
{
float value = (*UniformScales)[nIndex];
if ( UniformScales->IsValidIndex(nInsertIndex) )
UniformScales->Insert(value, nInsertIndex);
else
UniformScales->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
DuplicateRotScaleUScale(0, NumberOfCVs++);
// We need to update the closed parameter
FHoudiniApi::SetParmIntValue(
FHoudiniEngine::Get().GetSession(), ConnectedAssetId,
HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1);
}
// INTERPOLATION
if (CurveTypeValue == HAPI_CURVETYPE_NURBS)
{
// Closed NURBS have additional points reproducing the first ones
if (CurveClosed)
{
// Only the first one if the method is freehand ...
DuplicateRotScaleUScale(0, NumberOfCVs++);
if (CurveMethodValue != 2)
{
// ... but also the 2nd and 3rd if the method is CVs or Breakpoints.
DuplicateRotScaleUScale(1, NumberOfCVs++);
DuplicateRotScaleUScale(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..
DuplicateRotScaleUScale(0, NumberOfCVs++);
}
}
else if (CurveClosed)
{
// For the other methods, if the bezier curve is closed, the last point is the 1st
DuplicateRotScaleUScale(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++)
{
DuplicateRotScaleUScale(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);
bAddUniformScales = bAddUniformScales && (UniformScales->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;
if (bAddUniformScales)
PartInfos.attributeCounts[NewAttributesOwner] += 1;
// Sending the updated PartInfos
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 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;
std::vector< HAPI_StringHandle > sh_attributes(nOwnerAttributeCount);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 0,
(HAPI_AttributeOwner)nOwner,
&sh_attributes.front(), sh_attributes.size()), false);
for (int nAttribute = 0; nAttribute < sh_attributes.size(); nAttribute++)
{
const HAPI_StringHandle sh = sh_attributes[nAttribute];
if (sh == 0)
continue;
// Get the attribute name
FHoudiniEngineString sName(sh);
std::string attr_name;
sName.ToStdString(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(),
ConnectedAssetId, 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(),
ConnectedAssetId, 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(),
ConnectedAssetId, 0,
attr_name.c_str(), &attr_info), false);
// SET
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 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(),
ConnectedAssetId, 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(),
ConnectedAssetId, 0,
attr_name.c_str(),
&attr_info), false);
// SET
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 0,
attr_name.c_str(), &attr_info,
FloatData.GetData(),
0, attr_info.count), false);
}
break;
case HAPI_STORAGETYPE_STRING:
{
// Storing String Data
TArray<HAPI_StringHandle> StringHandleData;
StringHandleData.SetNumUninitialized(attr_info.count * attr_info.tupleSize);
// GET
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 0,
attr_name.c_str(), &attr_info,
StringHandleData.GetData(),
0, attr_info.count), false);
// Convert the SH to const char *
TArray<const char *> StringData;
StringData.SetNumUninitialized(attr_info.count);
for (int n = 0; n < StringHandleData.Num(); n++)
{
// Converting the string
FHoudiniEngineString strHE(sh);
std::string strSTD;
strHE.ToStdString(strSTD);
StringData[n] = strSTD.c_str();
}
// ADD
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 0,
attr_name.c_str(),
&attr_info), false);
// SET
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 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(),
ConnectedAssetId, 0,
&CurveInfo), false);
// ... the curve counts
TArray< int > CurveCounts;
CurveCounts.SetNumUninitialized(CurveInfo.curveCount);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveCounts(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 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(),
ConnectedAssetId, 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(),
ConnectedAssetId, 0,
KnotsArray.GetData(),
0, CurveInfo.knotCount), false);
}
// To set them back in HAPI
// CurveInfo
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveInfo(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 0,
&CurveInfo), false);
// CurveCounts
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveCounts(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 0,
CurveCounts.GetData(),
0, CurveInfo.curveCount), false);
// CurveOrders
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveOrders(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 0,
CurveOrders.GetData(),
0, CurveInfo.curveCount), false);
// And Knots if they exist
if (CurveInfo.hasKnots)
{
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveKnots(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 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(),
ConnectedAssetId, 0,
FaceCounts.GetData(), 0,
PartInfos.faceCount) == HAPI_RESULT_SUCCESS)
{
// Set the face count
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 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(),
ConnectedAssetId, 0,
VertexList.GetData(),
0, PartInfos.vertexCount) == HAPI_RESULT_SUCCESS)
{
// setting the vertex list
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 0,
VertexList.GetData(),
0, PartInfos.vertexCount), false);
}
}
// Get runtime settings.
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
if (HoudiniRuntimeSettings)
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
// 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);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(),
ConnectedAssetId, 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];
if (ImportAxis == HRSAI_Unreal)
{
CurveRotations[Idx * 4 + 0] = RotationQuaternion.X;
CurveRotations[Idx * 4 + 1] = RotationQuaternion.Z;
CurveRotations[Idx * 4 + 2] = RotationQuaternion.Y;
CurveRotations[Idx * 4 + 3] = -RotationQuaternion.W;
}
else if (ImportAxis == HRSAI_Houdini)
{
CurveRotations[Idx * 4 + 0] = RotationQuaternion.X;
CurveRotations[Idx * 4 + 1] = RotationQuaternion.Y;
CurveRotations[Idx * 4 + 2] = RotationQuaternion.Z;
CurveRotations[Idx * 4 + 3] = RotationQuaternion.W;
}
else
{
// Not valid enum value.
check(0);
}
}
//we can now upload them to our attribute.
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 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);
//FMemory::Memzero< HAPI_AttributeInfo >(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(),
ConnectedAssetId, 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];
if (ImportAxis == HRSAI_Unreal)
{
CurveScales[Idx * 3 + 0] = ScaleVector.X;
CurveScales[Idx * 3 + 1] = ScaleVector.Z;
CurveScales[Idx * 3 + 2] = ScaleVector.Y;
}
else if (ImportAxis == HRSAI_Houdini)
{
CurveScales[Idx * 3 + 0] = ScaleVector.X;
CurveScales[Idx * 3 + 1] = ScaleVector.Y;
CurveScales[Idx * 3 + 2] = ScaleVector.Z;
}
else
{
// Not valid enum value.
check(0);
}
}
// We can now upload them to our attribute.
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 0,
HAPI_UNREAL_ATTRIB_SCALE,
&AttributeInfoScale,
CurveScales.GetData(),
0, AttributeInfoScale.count), false);
}
// Create PSCALE attribute info.
if (bAddUniformScales)
{
HAPI_AttributeInfo AttributeInfoPScale;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoPScale);
//FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoPScale);
AttributeInfoPScale.count = NumberOfCVs;
AttributeInfoPScale.tupleSize = 1;
AttributeInfoPScale.exists = true;
AttributeInfoPScale.owner = NewAttributesOwner;
AttributeInfoPScale.storage = HAPI_STORAGETYPE_FLOAT;
AttributeInfoPScale.originalOwner = OriginalAttributesOwner;
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 0,
HAPI_UNREAL_ATTRIB_UNIFORM_SCALE,
&AttributeInfoPScale), false);
// Get the current uniform scale.
TArray<float> CurvePScales;
CurvePScales.SetNumZeroed(NumberOfCVs);
for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx)
{
CurvePScales[Idx] = (*UniformScales)[Idx];
}
// We can now upload them to our attribute.
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 0,
HAPI_UNREAL_ATTRIB_UNIFORM_SCALE,
&AttributeInfoPScale,
CurvePScales.GetData(),
0, AttributeInfoPScale.count), false);
}
// Finally, commit the geo ...
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo(
FHoudiniEngine::Get().GetSession(), ConnectedAssetId), false);
#endif
return true;
}
bool
FHoudiniEngineUtils::HapiGetAssetTransform( HAPI_NodeId AssetId, FTransform & InTransform )
{
HAPI_NodeInfo LocalAssetNodeInfo;
FHoudiniApi::NodeInfo_Init(&LocalAssetNodeInfo);
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo(
FHoudiniEngine::Get().GetSession(), AssetId,
&LocalAssetNodeInfo ), false );
HAPI_Transform LocalHapiTransform;
FHoudiniApi::Transform_Init(&LocalHapiTransform);
//FMemory::Memzero< HAPI_Transform >( LocalHapiTransform );
if ( LocalAssetNodeInfo.type == HAPI_NODETYPE_SOP )
{
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetObjectTransform(
FHoudiniEngine::Get().GetSession(), LocalAssetNodeInfo.parentId, -1,
HAPI_SRT, &LocalHapiTransform ), false );
}
else if ( LocalAssetNodeInfo.type == HAPI_NODETYPE_OBJ )
{
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetObjectTransform(
FHoudiniEngine::Get().GetSession(), AssetId, -1,
HAPI_SRT, &LocalHapiTransform ), false );
}
else
return false;
// Convert HAPI transform to Unreal one.
FHoudiniEngineUtils::TranslateHapiTransform( LocalHapiTransform, InTransform );
return true;
}
bool
FHoudiniEngineUtils::HapiGetNodeId( HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId, HAPI_NodeId & NodeId )
{
if ( FHoudiniEngineUtils::IsValidNodeId( AssetId ) )
{
HAPI_GeoInfo GeoInfo;
FHoudiniApi::GeoInfo_Init(&GeoInfo);
if ( FHoudiniApi::GetGeoInfo(
FHoudiniEngine::Get().GetSession(), GeoId, &GeoInfo ) == HAPI_RESULT_SUCCESS )
{
NodeId = GeoInfo.nodeId;
return true;
}
}
NodeId = -1;
return false;
}
bool
FHoudiniEngineUtils::HapiGetNodePath( HAPI_NodeId NodeId, HAPI_NodeId RelativeToNodeId, FString & OutPath )
{
if ( ( NodeId == -1 ) || ( RelativeToNodeId == -1 ) )
return false;
if ( !FHoudiniEngineUtils::IsHoudiniNodeValid( NodeId ) )
return false;
HAPI_StringHandle StringHandle;
if ( FHoudiniApi::GetNodePath(
FHoudiniEngine::Get().GetSession(),
NodeId, RelativeToNodeId, &StringHandle ) == HAPI_RESULT_SUCCESS )
{
FHoudiniEngineString HoudiniEngineString( StringHandle );
if ( HoudiniEngineString.ToFString( OutPath ) )
{
return true;
}
}
return false;
}
bool
FHoudiniEngineUtils::HapiGetObjectInfos( HAPI_NodeId AssetId, TArray< HAPI_ObjectInfo > & ObjectInfos )
{
HAPI_NodeInfo LocalAssetNodeInfo;
FHoudiniApi::NodeInfo_Init(&LocalAssetNodeInfo);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo(
FHoudiniEngine::Get().GetSession(), AssetId,
&LocalAssetNodeInfo), false);
int32 ObjectCount = 0;
if (LocalAssetNodeInfo.type == HAPI_NODETYPE_SOP)
{
ObjectCount = 1;
ObjectInfos.SetNumUninitialized(1);
FHoudiniApi::ObjectInfo_Init(&(ObjectInfos[0]));
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo(
FHoudiniEngine::Get().GetSession(), LocalAssetNodeInfo.parentId,
&ObjectInfos[0]), false);
}
else if (LocalAssetNodeInfo.type == HAPI_NODETYPE_OBJ)
{
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList(
FHoudiniEngine::Get().GetSession(), AssetId, nullptr, &ObjectCount), false);
if (ObjectCount <= 0)
{
ObjectCount = 1;
ObjectInfos.SetNumUninitialized(1);
FHoudiniApi::ObjectInfo_Init(&(ObjectInfos[0]));
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo(
FHoudiniEngine::Get().GetSession(), AssetId,
&ObjectInfos[0]), false);
}
else
{
ObjectInfos.SetNumUninitialized(ObjectCount);
for (int32 Idx = 0; Idx < ObjectInfos.Num(); Idx++ )
FHoudiniApi::ObjectInfo_Init(&(ObjectInfos[0]));
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectList(
FHoudiniEngine::Get().GetSession(), AssetId,
&ObjectInfos[0], 0, ObjectCount), false);
}
}
else
return false;
return true;
}
bool
FHoudiniEngineUtils::HapiGetObjectTransforms( HAPI_NodeId AssetId, TArray< HAPI_Transform > & ObjectTransforms )
{
HAPI_NodeInfo LocalAssetNodeInfo;
FHoudiniApi::NodeInfo_Init(&LocalAssetNodeInfo);
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo(
FHoudiniEngine::Get().GetSession(), AssetId,
&LocalAssetNodeInfo ), false );
int32 ObjectCount = 1;
ObjectTransforms.SetNumUninitialized( 1 );
FHoudiniApi::Transform_Init(&(ObjectTransforms[0]));
//FMemory::Memzero< HAPI_Transform >( ObjectTransforms[ 0 ] );
ObjectTransforms[ 0 ].rotationQuaternion[ 3 ] = 1.0f;
ObjectTransforms[ 0 ].scale[ 0 ] = 1.0f;
ObjectTransforms[ 0 ].scale[ 1 ] = 1.0f;
ObjectTransforms[ 0 ].scale[ 2 ] = 1.0f;
ObjectTransforms[ 0 ].rstOrder = HAPI_SRT;
if ( LocalAssetNodeInfo.type == HAPI_NODETYPE_SOP )
{
// Do nothing. Identity transform will be used for the main parent object.
}
else if ( LocalAssetNodeInfo.type == HAPI_NODETYPE_OBJ )
{
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ComposeObjectList(
FHoudiniEngine::Get().GetSession(), AssetId, nullptr, &ObjectCount ), false );
if ( ObjectCount <= 0 )
{
// Do nothing. Identity transform will be used for the main asset object.
}
else
{
ObjectTransforms.SetNumUninitialized( ObjectCount );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetComposedObjectTransforms(
FHoudiniEngine::Get().GetSession(), AssetId,
HAPI_SRT, &ObjectTransforms[ 0 ], 0, ObjectCount ), false );
}
}
else
return false;
return true;
}
bool
FHoudiniEngineUtils::HapiCreateInputNodeForLandscape(
const HAPI_NodeId& HostAssetId, ALandscapeProxy * LandscapeProxy,
HAPI_NodeId & ConnectedAssetId, TArray< HAPI_NodeId >& OutCreatedNodeIds,
const bool& bExportOnlySelected, const bool& bExportCurves,
const bool& bExportMaterials, const bool& bExportGeometryAsMesh,
const bool& bExportLighting, const bool& bExportNormalizedUVs,
const bool& bExportTileUVs, const FBox& AssetBounds,
const bool& bExportAsHeighfield, const bool& bAutoSelectComponents )
{
#if WITH_EDITOR
// If we don't have any landscapes or host asset is invalid then there's nothing to do.
if ( !LandscapeProxy || LandscapeProxy->IsPendingKill() || !FHoudiniEngineUtils::IsHoudiniNodeValid( HostAssetId ) )
return false;
// Get runtime settings.
float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION;
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
if ( HoudiniRuntimeSettings )
{
GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor;
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
}
TSet< ULandscapeComponent * > SelectedComponents;
if ( bExportOnlySelected )
{
const ULandscapeInfo * LandscapeInfo = LandscapeProxy->GetLandscapeInfo();
if ( LandscapeInfo && !LandscapeInfo->IsPendingKill() )
{
// Get the currently selected components
SelectedComponents = LandscapeInfo->GetSelectedComponents();
}
if ( bAutoSelectComponents && SelectedComponents.Num() <= 0 && AssetBounds.IsValid )
{
// We'll try to use the asset bounds to automatically "select" the components
for ( int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++ )
{
ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ ComponentIdx ];
if ( !LandscapeComponent || LandscapeComponent->IsPendingKill() )
continue;
FBoxSphereBounds WorldBounds = LandscapeComponent->CalcBounds( LandscapeComponent->GetComponentTransform());
if ( AssetBounds.IntersectXY( WorldBounds.GetBox() ) )
SelectedComponents.Add( LandscapeComponent );
}
int32 Num = SelectedComponents.Num();
HOUDINI_LOG_MESSAGE( TEXT("Landscape input: automatically selected %d components within the asset's bounds."), Num );
}
}
else
{
// Add all the components to the selected set
for ( int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++ )
{
ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ ComponentIdx ];
if ( !LandscapeComponent || LandscapeComponent->IsPendingKill() )
continue;
SelectedComponents.Add( LandscapeComponent );
}
}
//--------------------------------------------------------------------------------------------------
// EXPORT TO HEIGHTFIELD
//--------------------------------------------------------------------------------------------------
if ( bExportAsHeighfield )
{
bool bSuccess = false;
HAPI_NodeId CreatedHeightfieldNodeId = -1;
int32 NumComponents = LandscapeProxy->LandscapeComponents.Num();
if ( !bExportOnlySelected || ( SelectedComponents.Num() == NumComponents ) )
{
// Export the whole landscape and its layer as a single heightfield node
bSuccess = FHoudiniLandscapeUtils::CreateHeightfieldFromLandscape( LandscapeProxy, CreatedHeightfieldNodeId );
}
else
{
// Each selected landscape component will be exported as separate volumes in a single heightfield
bSuccess = FHoudiniLandscapeUtils::CreateHeightfieldFromLandscapeComponentArray( LandscapeProxy, SelectedComponents, CreatedHeightfieldNodeId );
}
// Add the Heightfield's parent OBJ node to the created nodes
OutCreatedNodeIds.AddUnique( FHoudiniEngineUtils::HapiGetParentNodeId( CreatedHeightfieldNodeId ) );
ConnectedAssetId = CreatedHeightfieldNodeId;
return bSuccess;
}
//--------------------------------------------------------------------------------------------------
// EXPORT TO MESH / POINTS
//--------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------
// 1. Create an input node if needed
//--------------------------------------------------------------------------------------------------
// Check if connected asset id is invalid, if it is not, we need to create an input node.
if ( ConnectedAssetId < 0 )
{
HAPI_NodeId InputNodeId = -1;
// Create the curve SOP Node
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateInputNode(
FHoudiniEngine::Get().GetSession(), &InputNodeId, nullptr ), 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.
ConnectedAssetId = InputNodeId;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode(
FHoudiniEngine::Get().GetSession(), InputNodeId, nullptr ), 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 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(), ConnectedAssetId, &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<FVector> LandscapePositionArray;
// Array for the normals
TArray<FVector> LandscapeNormalArray;
// Array for the UVs
TArray<FVector> LandscapeUVArray;
// Array for the vertex index of each point in its component
TArray<FIntPoint> LandscapeComponentVertexIndicesArray;
// Array for the tile names per point
TArray<const char *> LandscapeComponentNameArray;
// Array for the lightmap values
TArray<FLinearColor> LandscapeLightmapValues;
// Extract all the data from the landscape to the arrays
if ( !FHoudiniLandscapeUtils::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 ( !FHoudiniLandscapeUtils::AddLandscapePositionAttribute( DisplayGeoInfo.nodeId, LandscapePositionArray ) )
return false;
// Create point attribute info containing normals.
if ( !FHoudiniLandscapeUtils::AddLandscapeNormalAttribute( DisplayGeoInfo.nodeId, LandscapeNormalArray ) )
return false;
// Create point attribute info containing UVs.
if ( !FHoudiniLandscapeUtils::AddLandscapeUVAttribute( DisplayGeoInfo.nodeId, LandscapeUVArray ) )
return false;
// Create point attribute containing landscape component vertex indices (indices of vertices within the grid - x,y).
if ( !FHoudiniLandscapeUtils::AddLandscapeComponentVertexIndicesAttribute( DisplayGeoInfo.nodeId, LandscapeComponentVertexIndicesArray ) )
return false;
// Create point attribute containing landscape component name.
if ( !FHoudiniLandscapeUtils::AddLandscapeComponentNameAttribute( DisplayGeoInfo.nodeId, LandscapeComponentNameArray ) )
return false;
// Create point attribute info containing lightmap information.
if ( bExportLighting )
{
if ( !FHoudiniLandscapeUtils::AddLandscapeLightmapColorAttribute( DisplayGeoInfo.nodeId, LandscapeLightmapValues ) )
return false;
}
// Set indices if we are exporting full geometry.
if ( bExportGeometryAsMesh )
{
if (!FHoudiniLandscapeUtils::AddLandscapeMeshIndicesAndMaterialsAttribute(
DisplayGeoInfo.nodeId, bExportMaterials,
ComponentSizeQuads, QuadCount,
LandscapeProxy, SelectedComponents ) )
return false;
}
// If we are marshalling material information.
if ( bExportMaterials )
{
if ( !FHoudiniLandscapeUtils::AddLandscapeGlobalMaterialAttribute( DisplayGeoInfo.nodeId, LandscapeProxy ) )
return false;
}
// Commit the geo.
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(
FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId ), false );
#endif
return true;
}
bool
FHoudiniEngineUtils::HapiCreateInputNodeForSpline(
HAPI_NodeId HostAssetId,
USplineComponent * SplineComponent,
HAPI_NodeId & ConnectedAssetId,
FHoudiniAssetInputOutlinerMesh& OutlinerMesh,
const float& SplineResolution )
{
#if WITH_EDITOR
// If we don't have a spline component, or host asset is invalid, there's nothing to do.
if ( !SplineComponent || SplineComponent->IsPendingKill() || !FHoudiniEngineUtils::IsHoudiniNodeValid( HostAssetId ) )
return false;
float fSplineResolution = SplineResolution;
if (fSplineResolution == -1.0f)
{
// Get runtime settings and extract the spline resolution from it
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
if (HoudiniRuntimeSettings)
fSplineResolution = HoudiniRuntimeSettings->MarshallingSplineResolution;
else
fSplineResolution = HAPI_UNREAL_PARAM_SPLINE_RESOLUTION_DEFAULT;
}
int32 nNumberOfControlPoints = SplineComponent->GetNumberOfSplinePoints();
float fSplineLength = SplineComponent->GetSplineLength();
// Calculate the number of refined point we want
int32 nNumberOfRefinedSplinePoints = fSplineResolution > 0.0f ? ceil(fSplineLength / fSplineResolution) + 1 : nNumberOfControlPoints;
// Array that will store the attributes we want to add to the curves
TArray<FVector> tRefinedSplinePositions;
TArray<FQuat> tRefinedSplineRotations;
// Scale on Unreal's spline will require some tweaking, as the XScale is always 1
TArray<FVector> tRefinedSplineScales;
if ( (nNumberOfRefinedSplinePoints < nNumberOfControlPoints) || (fSplineResolution <= 0.0f) )
{
// There's not enough refined points, so we'll use the Spline CVs instead
tRefinedSplinePositions.SetNumZeroed(nNumberOfControlPoints);
tRefinedSplineRotations.SetNumZeroed(nNumberOfControlPoints);
tRefinedSplineScales.SetNumZeroed(nNumberOfControlPoints);
FVector Scale;
for (int32 n = 0; n < nNumberOfControlPoints; n++)
{
tRefinedSplinePositions[n] = SplineComponent->GetLocationAtSplinePoint(n, ESplineCoordinateSpace::Local);
tRefinedSplineRotations[n] = SplineComponent->GetQuaternionAtSplinePoint(n, ESplineCoordinateSpace::World);
Scale = SplineComponent->GetScaleAtSplinePoint(n);
tRefinedSplineScales[n] = Scale;
}
}
else
{
// Calculating the refined spline points
tRefinedSplinePositions.SetNumZeroed(nNumberOfRefinedSplinePoints);
tRefinedSplineRotations.SetNumZeroed(nNumberOfRefinedSplinePoints);
tRefinedSplineScales.SetNumZeroed(nNumberOfRefinedSplinePoints);
FVector Scale;
float fCurrentDistance = 0.0f;
for (int32 n = 0; n < nNumberOfRefinedSplinePoints; n++)
{
tRefinedSplinePositions[n] = SplineComponent->GetLocationAtDistanceAlongSpline(fCurrentDistance, ESplineCoordinateSpace::Local);
tRefinedSplineRotations[n] = SplineComponent->GetQuaternionAtDistanceAlongSpline(fCurrentDistance, ESplineCoordinateSpace::World);
Scale = SplineComponent->GetScaleAtDistanceAlongSpline(fCurrentDistance);
tRefinedSplineScales[n] = Scale;
fCurrentDistance += fSplineResolution;
}
}
if ( !HapiCreateCurveInputNodeForData(
HostAssetId,
ConnectedAssetId,
&tRefinedSplinePositions,
&tRefinedSplineRotations,
&tRefinedSplineScales,
nullptr,
SplineComponent->IsClosedLoop() ) )
return false;
// Updating the OutlinerMesh's struct infos
OutlinerMesh.SplineResolution = fSplineResolution;
OutlinerMesh.SplineLength = fSplineLength;
OutlinerMesh.NumberOfSplineControlPoints = nNumberOfControlPoints;
// We also need to extract all the CV's Transform to check for modifications
OutlinerMesh.SplineControlPointsTransform.SetNum( nNumberOfControlPoints );
for ( int32 n = 0; n < nNumberOfControlPoints; n++ )
{
// Getting the Local Transform for positions and scale
OutlinerMesh.SplineControlPointsTransform[ n ] = SplineComponent->GetTransformAtSplinePoint( n, ESplineCoordinateSpace::Local, true );
// ... but we used we used the world rotation
OutlinerMesh.SplineControlPointsTransform[ n ].SetRotation( SplineComponent->GetQuaternionAtSplinePoint(n, ESplineCoordinateSpace::World ) );
}
// Add the spline component's tag if it has some
bool NeedToCommit = FHoudiniEngineUtils::CreateGroupOrAttributeFromTags( ConnectedAssetId, 0, SplineComponent->ComponentTags, false);
// Add the parent actor's tag if it has some
AActor* ParentActor = SplineComponent->GetOwner();
if (ParentActor && !ParentActor->IsPendingKill())
{
// Try to create groups for the parent Actor's tags
if ( FHoudiniEngineUtils::CreateGroupOrAttributeFromTags( ConnectedAssetId, 0, ParentActor->Tags, false ) )
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(), ConnectedAssetId) )
HOUDINI_LOG_WARNING( TEXT("Could not create groups for the spline input's tags!") );
}
// Cook the spline node.
FHoudiniApi::CookNode( FHoudiniEngine::Get().GetSession(), ConnectedAssetId, nullptr );
#endif
return true;
}
bool
FHoudiniEngineUtils::HapiCreateInputNodeForStaticMesh(
UStaticMesh * StaticMesh,
HAPI_NodeId & ConnectedAssetId,
TArray< HAPI_NodeId >& OutCreatedNodeIds,
UStaticMeshComponent* StaticMeshComponent /* = nullptr */,
const bool& ExportAllLODs /* = false */,
const bool& ExportSockets /* = false */)
{
#if WITH_EDITOR
// If we don't have a static mesh there's nothing to do.
if ( !StaticMesh || StaticMesh->IsPendingKill() )
return false;
// 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 );
// We need to use a merge node if we export lods OR sockets
bool UseMergeNode = DoExportLODs || DoExportSockets;
if ( UseMergeNode )
{
// Create a merge SOP asset. This will be our "ConnectedAssetId".
// All the different LOD meshes will be plugged into it
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), -1,
"SOP/merge", "input", true, &ConnectedAssetId ), false );
}
else if ( ConnectedAssetId < 0 )
{
// No LOD, we need a single input node
// If connected asset id is invalid, we need to create an input node.
HAPI_NodeId InputNodeId = -1;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateInputNode(
FHoudiniEngine::Get().GetSession(), &InputNodeId, nullptr ), 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.
ConnectedAssetId = InputNodeId;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode(
FHoudiniEngine::Get().GetSession(), InputNodeId, nullptr ), false );
}
// Get the input's parent OBJ node
HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId( ConnectedAssetId );
OutCreatedNodeIds.AddUnique( ParentId );
// Get runtime settings.
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
check( HoudiniRuntimeSettings );
float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION;
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
int32 GeneratedLightMapResolution = 32;
if ( HoudiniRuntimeSettings )
{
GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor;
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution;
}
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 );
}
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), ParentId, "null", LODName, false, &CurrentLODNodeId ), false );
}
else
{
// No merge node, just use the input node we created before
CurrentLODNodeId = ConnectedAssetId;
}
// Load the existing raw mesh.
FRawMesh RawMesh;
SrcModel.LoadRawMesh(RawMesh);
// Create part.
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 = 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(), CurrentLODNodeId, 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(), CurrentLODNodeId, 0,
HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint ), false );
// Grab the build scale
FVector BuildScaleVector = SrcModel.BuildSettings.BuildScale3D;
// Extract vertices from static mesh.
TArray< float > StaticMeshVertices;
StaticMeshVertices.SetNumZeroed( RawMesh.VertexPositions.Num() * 3 );
for ( int32 VertexIdx = 0; VertexIdx < RawMesh.VertexPositions.Num(); ++VertexIdx )
{
// Grab vertex at this index.
const FVector & PositionVector = RawMesh.VertexPositions[ VertexIdx ];
if ( ImportAxis == HRSAI_Unreal )
{
StaticMeshVertices[ VertexIdx * 3 + 0 ] = PositionVector.X / GeneratedGeometryScaleFactor * BuildScaleVector.X;
StaticMeshVertices[ VertexIdx * 3 + 1 ] = PositionVector.Z / GeneratedGeometryScaleFactor * BuildScaleVector.Z;
StaticMeshVertices[ VertexIdx * 3 + 2 ] = PositionVector.Y / GeneratedGeometryScaleFactor * BuildScaleVector.Y;
}
else if ( ImportAxis == HRSAI_Houdini )
{
StaticMeshVertices[ VertexIdx * 3 + 0 ] = PositionVector.X / GeneratedGeometryScaleFactor * BuildScaleVector.X;
StaticMeshVertices[ VertexIdx * 3 + 1 ] = PositionVector.Y / GeneratedGeometryScaleFactor * BuildScaleVector.Y;
StaticMeshVertices[ VertexIdx * 3 + 2 ] = PositionVector.Z / GeneratedGeometryScaleFactor * BuildScaleVector.Z;
}
else
{
// Not valid enum value.
check( 0 );
}
}
// Now that we have raw positions, we can upload them for our attribute.
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(), CurrentLODNodeId,
0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint,
StaticMeshVertices.GetData(), 0,
AttributeInfoPoint.count ), false );
// See if we have texture coordinates to upload.
for ( int32 MeshTexCoordIdx = 0; MeshTexCoordIdx < MAX_STATIC_TEXCOORDS; ++MeshTexCoordIdx )
{
int32 StaticMeshUVCount = RawMesh.WedgeTexCoords[ MeshTexCoordIdx ].Num();
if ( StaticMeshUVCount > 0 )
{
const TArray< FVector2D > & RawMeshUVs = RawMesh.WedgeTexCoords[ MeshTexCoordIdx ];
TArray< FVector > 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 );
if ( ImportAxis == HRSAI_Unreal )
{
// We need to re-index UVs for wedges we swapped (due to winding differences).
for ( int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3 )
{
// We do not touch wedge 0 of this triangle.
StaticMeshUVs.SwapMemory( WedgeIdx + 1, WedgeIdx + 2 );
}
}
else if ( ImportAxis == HRSAI_Houdini )
{
// Do nothing, data is in proper format.
}
else
{
// Not valid enum value.
check( 0 );
}
// Construct attribute name for this 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);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(), CurrentLODNodeId,
0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
CurrentLODNodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex,
(const float *) StaticMeshUVs.GetData(), 0, AttributeInfoVertex.count ), false );
}
}
// See if we have normals to upload.
if ( RawMesh.WedgeTangentZ.Num() > 0 )
{
TArray< FVector > ChangedNormals( RawMesh.WedgeTangentZ );
if ( ImportAxis == HRSAI_Unreal )
{
// 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 < RawMesh.WedgeIndices.Num(); ++WedgeIdx )
Swap( ChangedNormals[ WedgeIdx ].Y, ChangedNormals[ WedgeIdx ].Z );
}
// Create attribute for normals.
HAPI_AttributeInfo AttributeInfoVertex;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(), CurrentLODNodeId,
0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
CurrentLODNodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex,
(const float *) ChangedNormals.GetData(),
0, AttributeInfoVertex.count ), false );
}
// See if we have tangentu to upload.
if (RawMesh.WedgeTangentX.Num() > 0)
{
TArray< FVector > ChangedTangentU(RawMesh.WedgeTangentX);
if (ImportAxis == HRSAI_Unreal)
{
// 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);
//FMemory::Memzero< HAPI_AttributeInfo >(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(), CurrentLODNodeId,
0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
CurrentLODNodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex,
(const float *)ChangedTangentU.GetData(),
0, AttributeInfoVertex.count), false);
}
// See if we have tangentv to upload.
if (RawMesh.WedgeTangentY.Num() > 0)
{
TArray< FVector > ChangedTangentV(RawMesh.WedgeTangentY);
if (ImportAxis == HRSAI_Unreal)
{
// 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);
//FMemory::Memzero< HAPI_AttributeInfo >(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(), CurrentLODNodeId,
0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
CurrentLODNodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex,
(const float *)ChangedTangentV.GetData(),
0, AttributeInfoVertex.count), false);
}
{
// If we have instance override vertex colors, first propagate them to our copy of
// the RawMesh Vert Colors
TArray< FLinearColor > ChangedColors;
if ( StaticMeshComponent &&
StaticMeshComponent->LODData.IsValidIndex( LODIndex ) &&
StaticMeshComponent->LODData[LODIndex].OverrideVertexColors &&
StaticMesh->RenderData &&
StaticMesh->RenderData->LODResources.IsValidIndex( LODIndex ) )
{
FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[LODIndex];
FStaticMeshRenderData& RenderData = *StaticMesh->RenderData;
FStaticMeshLODResources& RenderModel = RenderData.LODResources[LODIndex];
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() );
if ( ImportAxis == HRSAI_Unreal )
{
// 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();
}
}
else if ( ImportAxis == HRSAI_Houdini )
{
for ( int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); ++WedgeIdx )
{
ChangedColors[WedgeIdx] = RawMesh.WedgeColors[WedgeIdx].ReinterpretAsLinear();
}
}
else
{
// Not valid enum value.
check( 0 );
}
}
if ( ChangedColors.Num() > 0 )
{
// Extract the RGB colors
TArray<float> 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);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(), CurrentLODNodeId,
0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
CurrentLODNodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex,
ColorValues.GetData(), 0, AttributeInfoVertex.count ), false );
// Create the attribute for Alpha
TArray<float> AlphaValues;
AlphaValues.SetNum(ChangedColors.Num());
for (int32 alphaIndex = 0; alphaIndex < ChangedColors.Num(); alphaIndex++)
AlphaValues[alphaIndex] = ChangedColors[alphaIndex].A;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex);
//FMemory::Memzero< HAPI_AttributeInfo >(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(), CurrentLODNodeId,
0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
CurrentLODNodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex,
AlphaValues.GetData(), 0, AttributeInfoVertex.count), false);
}
}
// Extract indices from static mesh.
if ( RawMesh.WedgeIndices.Num() > 0 )
{
TArray< int32 > StaticMeshIndices;
StaticMeshIndices.SetNumUninitialized( RawMesh.WedgeIndices.Num() );
if ( ImportAxis == HRSAI_Unreal )
{
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 ];
}
}
else if ( ImportAxis == HRSAI_Houdini )
{
StaticMeshIndices = TArray< int32 >( RawMesh.WedgeIndices );
}
else
{
// Not valid enum value.
check( 0 );
}
// We can now set vertex list.
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetVertexList(
FHoudiniEngine::Get().GetSession(), CurrentLODNodeId,
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(), CurrentLODNodeId,
0, StaticMeshFaceCounts.GetData(), 0, StaticMeshFaceCounts.Num() ), false );
}
// Marshall face material indices.
if ( RawMesh.FaceMaterialIndices.Num() > 0 )
{
// TODO: FIX ME PROPERLY
// In some cases, deleted/unused materials could cause crashes in FHoudiniEngineUtils::CreateFaceMaterialArray() later
// To avoid this, we need to make sure that the MaterialInterfaces array size matches the face material indexes..
// Proper fix would be to export the mesh via section...
int32 MaxMatIndex = 0;
TArray< UMaterialInterface * > MaterialInterfaces;
if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel() )
{
//StaticMeshComponent->GetUsedMaterials(MaterialInterfaces, false);
{
// Get the map of material used for the static mesh component
TMap<int32, UMaterialInterface*> MapOfMaterials;
for (int32 LODIdx = 0; LODIdx < StaticMeshComponent->GetStaticMesh()->RenderData->LODResources.Num(); LODIdx++)
{
FStaticMeshLODResources& LODResources = StaticMeshComponent->GetStaticMesh()->RenderData->LODResources[LODIdx];
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, StaticMeshComponent->GetMaterial(MaterialIndex));
// Keep track of the max material index
if (MaxMatIndex < MaterialIndex)
MaxMatIndex = MaterialIndex;
}
}
}
// We need to assign enough materials for our index
MaterialInterfaces.SetNum(MaxMatIndex + 1);
for (const auto& Kvp : MapOfMaterials)
{
MaterialInterfaces[Kvp.Key] = Kvp.Value;
}
}
// TODO: FIX ME PROPERLY
// Trying to fix up inconsistencies between the RawMesh / StaticMesh material indexes by using the meshes sections...
int NumMeshBasedMtrls = StaticMeshComponent->GetNumMaterials();
TArray< UMaterialInterface * > MeshBasedMaterialInterfaces;
MeshBasedMaterialInterfaces.SetNumUninitialized(NumMeshBasedMtrls);
for (int i = 0; i < NumMeshBasedMtrls; i++)
MeshBasedMaterialInterfaces[i] = StaticMeshComponent->GetMaterial(i);
int32 NumSections = StaticMesh->GetNumSections(LODIndex);
if ( NumSections > NumMeshBasedMtrls )
MaterialInterfaces.SetNumUninitialized(NumSections);
for (int SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
{
FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get(LODIndex, SectionIndex);
check(Info.MaterialIndex < NumMeshBasedMtrls);
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(LODIndex) )
{
TMap<int32, UMaterialInterface*> MapOfMaterials;
FStaticMeshLODResources& LODResources = StaticMesh->RenderData->LODResources[LODIndex];
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< char * > StaticMeshFaceMaterials;
FHoudiniEngineUtils::CreateFaceMaterialArray(
MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials );
// Get name of attribute used for marshalling materials.
std::string MarshallingAttributeName = HAPI_UNREAL_ATTRIB_MATERIAL;
if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeMaterial.IsEmpty() )
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeMaterial, MarshallingAttributeName );
// Create attribute for materials.
HAPI_AttributeInfo AttributeInfoMaterial;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoMaterial );
AttributeInfoMaterial.count = RawMesh.FaceMaterialIndices.Num();
AttributeInfoMaterial.tupleSize = 1;
AttributeInfoMaterial.exists = true;
AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM;
AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING;
AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID;
bool bAttributeError = false;
if ( FHoudiniApi::AddAttribute( FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, 0,
MarshallingAttributeName.c_str(), &AttributeInfoMaterial ) != HAPI_RESULT_SUCCESS )
{
bAttributeError = true;
}
if ( FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(),
CurrentLODNodeId, 0, MarshallingAttributeName.c_str(), &AttributeInfoMaterial,
(const char **) StaticMeshFaceMaterials.GetData(), 0,
StaticMeshFaceMaterials.Num() ) != HAPI_RESULT_SUCCESS )
{
bAttributeError = true;
}
// Delete material names.
FHoudiniEngineUtils::DeleteFaceMaterialArray( StaticMeshFaceMaterials );
if ( bAttributeError )
{
check( 0 );
return false;
}
}
// Marshall face smoothing masks.
if ( RawMesh.FaceSmoothingMasks.Num() > 0 )
{
// Get name of attribute used for marshalling face smoothing masks.
std::string MarshallingAttributeName = HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK;
if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeMaterial.IsEmpty() )
{
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeFaceSmoothingMask,
MarshallingAttributeName );
}
HAPI_AttributeInfo AttributeInfoSmoothingMasks;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(), CurrentLODNodeId,
0, MarshallingAttributeName.c_str(), &AttributeInfoSmoothingMasks ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeIntData(
FHoudiniEngine::Get().GetSession(),
CurrentLODNodeId, 0, MarshallingAttributeName.c_str(), &AttributeInfoSmoothingMasks,
(const int32 *) RawMesh.FaceSmoothingMasks.GetData(), 0, RawMesh.FaceSmoothingMasks.Num() ), false );
}
// Marshall lightmap resolution.
if ( StaticMesh->LightMapResolution != GeneratedLightMapResolution )
{
std::string MarshallingAttributeName = HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION;
if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeLightmapResolution.IsEmpty() )
{
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeLightmapResolution,
MarshallingAttributeName );
}
TArray< int32 > LightMapResolutions;
LightMapResolutions.Add( StaticMesh->LightMapResolution );
HAPI_AttributeInfo AttributeInfoLightMapResolution;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(), CurrentLODNodeId,
0, MarshallingAttributeName.c_str(), &AttributeInfoLightMapResolution ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeIntData(
FHoudiniEngine::Get().GetSession(),
CurrentLODNodeId, 0, MarshallingAttributeName.c_str(), &AttributeInfoLightMapResolution,
(const int32 *) LightMapResolutions.GetData(), 0, LightMapResolutions.Num() ), false );
}
if ( !HoudiniRuntimeSettings->MarshallingAttributeInputMeshName.IsEmpty() )
{
// 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<const char*> PrimitiveAttrs;
PrimitiveAttrs.AddUninitialized( Part.faceCount );
for ( int32 Ix = 0; Ix < Part.faceCount; ++Ix )
{
PrimitiveAttrs[ Ix ] = MeshAssetPathRaw;
}
std::string MarshallingAttributeName;
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeInputMeshName,
MarshallingAttributeName );
HAPI_AttributeInfo AttributeInfo;
FHoudiniApi::AttributeInfo_Init(&AttributeInfo);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(), CurrentLODNodeId,
0, MarshallingAttributeName.c_str(), &AttributeInfo ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(),
CurrentLODNodeId, 0, MarshallingAttributeName.c_str(), &AttributeInfo,
PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num() ), false );
}
if( !HoudiniRuntimeSettings->MarshallingAttributeInputSourceFile.IsEmpty() )
{
FString Filename;
// Create primitive attribute with mesh asset path
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<const char*> PrimitiveAttrs;
PrimitiveAttrs.AddUninitialized( Part.faceCount );
for( int32 Ix = 0; Ix < Part.faceCount; ++Ix )
{
PrimitiveAttrs[Ix] = FilenameCStrRaw;
}
std::string MarshallingAttributeName;
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeInputSourceFile,
MarshallingAttributeName );
HAPI_AttributeInfo AttributeInfo;
FHoudiniApi::AttributeInfo_Init(&AttributeInfo);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(), CurrentLODNodeId,
0, MarshallingAttributeName.c_str(), &AttributeInfo ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(),
CurrentLODNodeId, 0, MarshallingAttributeName.c_str(), &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<UHoudiniAttributeDataComponent>() )
{
bool bSuccess = DataComponent->Upload( CurrentLODNodeId, StaticMeshComponent );
if ( !bSuccess )
{
HOUDINI_LOG_ERROR( TEXT( "Upload of attribute data for %s failed" ), *StaticMeshComponent->GetOwner()->GetName() );
}
}
}
if ( DoExportLODs )
{
// LOD Group
const char * LODGroupStr = "";
{
FString LODGroup = TEXT("lod") + FString::FromInt(LODIndex);
LODGroupStr = TCHAR_TO_UTF8(*LODGroup);
}
// Add a LOD group
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddGroup(
FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, 0,
HAPI_GROUPTYPE_PRIM, LODGroupStr), false);
// Set GroupMembership
TArray<int> GroupArray;
GroupArray.Init(1, Part.faceCount);
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetGroupMembership(
FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, 0,
HAPI_GROUPTYPE_PRIM, LODGroupStr, GroupArray.GetData(), 0, Part.faceCount ), false );
if ( !StaticMesh->bAutoComputeLODScreenSize )
{
// Add the lodX_screensize
FString LODAttributeName = TEXT("lod") + FString::FromInt( LODIndex ) + TEXT("_screensize");
// Create screensize detail attribute info.
HAPI_AttributeInfo AttributeInfoLODScreenSize;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(), CurrentLODNodeId, 0,
TCHAR_TO_UTF8( *LODAttributeName ), &AttributeInfoLODScreenSize), false);
float lodscreensize = SrcModel.ScreenSize.Default;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(), CurrentLODNodeId, 0,
TCHAR_TO_UTF8( *LODAttributeName ), &AttributeInfoLODScreenSize,
&lodscreensize, 0, 1 ), false);
}
}
if ( StaticMeshComponent && !StaticMeshComponent->IsPendingKill() )
{
// Try to create groups for the static mesh component's tags
if ( StaticMeshComponent->ComponentTags.Num() > 0
&& !FHoudiniEngineUtils::CreateGroupOrAttributeFromTags( CurrentLODNodeId, 0, StaticMeshComponent->ComponentTags, false ) )
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::CreateGroupOrAttributeFromTags( CurrentLODNodeId, 0, ParentActor->Tags, false ) )
HOUDINI_LOG_WARNING( TEXT("Could not create groups from the Static Mesh Component's parent actor tags!") );
}
}
// Commit the geo.
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(
FHoudiniEngine::Get().GetSession(), CurrentLODNodeId), false );
if ( UseMergeNode )
{
// Now we can connect the LOD node to the input node.
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput(
FHoudiniEngine::Get().GetSession(), ConnectedAssetId, LODIndex,
CurrentLODNodeId, 0), false);
}
}
if ( DoExportSockets )
{
int32 NumSockets = StaticMesh->Sockets.Num();
HAPI_NodeId SocketsNodeId = -1;
// Create a new input node for the sockets
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), ParentId, "null", "sockets", false, &SocketsNodeId ), false );
// Create part.
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.pointCount = NumSockets;
Part.vertexCount = 0;
Part.faceCount = 0;
Part.type = HAPI_PARTTYPE_MESH;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetPartInfo(
FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0, &Part), false);
// Create POS point attribute info.
HAPI_AttributeInfo AttributeInfoPos;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(), SocketsNodeId, 0,
HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos ), false );
// Create Rot point attribute Info
HAPI_AttributeInfo AttributeInfoRot;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(), SocketsNodeId, 0,
HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot ), false );
// Create scale point attribute Info
HAPI_AttributeInfo AttributeInfoScale;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(), SocketsNodeId, 0,
HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale ), false );
// Create the name attrib info
HAPI_AttributeInfo AttributeInfoName;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoName);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(), SocketsNodeId, 0,
HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName ), false );
// Create the tag attrib info
HAPI_AttributeInfo AttributeInfoTag;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(), SocketsNodeId, 0,
HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag ), false );
// Extract the sockets transform values
TArray< float > SocketPos;
SocketPos.SetNumZeroed( NumSockets * 3 );
TArray< float > SocketRot;
SocketRot.SetNumZeroed( NumSockets * 4 );
TArray< float > SocketScale;
SocketScale.SetNumZeroed( NumSockets * 3 );
TArray< const char * > SocketNames;
TArray< const char * > SocketTags;
for ( int32 Idx = 0; Idx < NumSockets; ++Idx )
{
UStaticMeshSocket* CurrentSocket = StaticMesh->Sockets[ 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);
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::ExtractRawName( CurrentSocketName ) );
if ( !CurrentSocket->Tag.IsEmpty() )
SocketTags.Add( FHoudiniEngineUtils::ExtractRawName( CurrentSocket->Tag ) );
else
SocketTags.Add( "" );
}
//we can now upload them to our attribute.
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
SocketsNodeId, 0,
HAPI_UNREAL_ATTRIB_POSITION,
&AttributeInfoPos,
SocketPos.GetData(),
0, AttributeInfoPos.count ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
SocketsNodeId, 0,
HAPI_UNREAL_ATTRIB_ROTATION,
&AttributeInfoRot,
SocketRot.GetData(),
0, AttributeInfoRot.count ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
SocketsNodeId, 0,
HAPI_UNREAL_ATTRIB_SCALE,
&AttributeInfoScale,
SocketScale.GetData(),
0, AttributeInfoScale.count ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(),
SocketsNodeId, 0,
HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME,
&AttributeInfoName,
SocketNames.GetData(),
0, AttributeInfoName.count ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(),
SocketsNodeId, 0,
HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG,
&AttributeInfoTag,
SocketTags.GetData(),
0, AttributeInfoTag.count ), 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.
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPos );
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(), SocketsNodeId, 0,
TCHAR_TO_ANSI( *PosAttr ), &AttributeInfoPos ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
SocketsNodeId, 0,
TCHAR_TO_ANSI( *PosAttr ),
&AttributeInfoPos,
&(SocketPos[ 3 * Idx ]),
0, AttributeInfoPos.count ), false );
// Create mesh_socketX_rot point attribute Info
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoRot );
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(), SocketsNodeId, 0,
TCHAR_TO_ANSI( *RotAttr ), &AttributeInfoRot ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
SocketsNodeId, 0,
TCHAR_TO_ANSI( *RotAttr ),
&AttributeInfoRot,
&(SocketRot[ 4 * Idx ]),
0, AttributeInfoRot.count ), false );
// Create mesh_socketX_scale point attribute Info
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoScale );
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(), SocketsNodeId, 0,
TCHAR_TO_ANSI( *ScaleAttr ), &AttributeInfoScale ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
SocketsNodeId, 0,
TCHAR_TO_ANSI( *ScaleAttr ),
&AttributeInfoScale,
&(SocketScale[ 3 * Idx ]),
0, AttributeInfoScale.count ), false );
// Create the mesh_socketX_name attrib info
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoName );
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(), SocketsNodeId, 0,
TCHAR_TO_ANSI( *NameAttr ), &AttributeInfoName ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(),
SocketsNodeId, 0,
TCHAR_TO_ANSI( *NameAttr ),
&AttributeInfoName,
&(SocketNames[ Idx ]),
0, AttributeInfoName.count ), false );
// Create the mesh_socketX_tag attrib info
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoTag );
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(), SocketsNodeId, 0,
TCHAR_TO_ANSI( *TagAttr ), &AttributeInfoTag ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(),
SocketsNodeId, 0,
TCHAR_TO_ANSI( *TagAttr ),
&AttributeInfoTag,
& ( SocketTags[ Idx ] ),
0, AttributeInfoTag.count ), false );
}
// Now add the sockets group
const char * SocketGroupStr = "socket_imported";
// Add a LOD group
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddGroup(
FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0,
HAPI_GROUPTYPE_POINT, SocketGroupStr ), false );
// Set GroupMembership
TArray<int> GroupArray;
GroupArray.Init( 1, NumSockets );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetGroupMembership(
FHoudiniEngine::Get().GetSession(), SocketsNodeId, 0,
HAPI_GROUPTYPE_POINT, SocketGroupStr, GroupArray.GetData(), 0, NumSockets ), false );
// Commit the geo.
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(
FHoudiniEngine::Get().GetSession(), SocketsNodeId ), false);
// Now we can connect the socket node to the merge node's last input.
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput(
FHoudiniEngine::Get().GetSession(), ConnectedAssetId, NumLODsToExport,
SocketsNodeId, 0), false );
}
#endif
return true;
}
bool
FHoudiniEngineUtils::HapiCreateInputNodeForWorldOutliner(
HAPI_NodeId HostAssetId,
TArray< FHoudiniAssetInputOutlinerMesh > & OutlinerMeshArray,
HAPI_NodeId & ConnectedAssetId,
TArray< HAPI_NodeId >& OutCreatedNodeIds,
const float& SplineResolution,
const bool& ExportAllLODs /* = false */,
const bool& ExportSockets /* = false */)
{
#if WITH_EDITOR
if ( OutlinerMeshArray.Num() <= 0 )
return false;
bool UseMerge = OutlinerMeshArray.Num() > 0;
if ( UseMerge )
{
// If we have more than one input, create the merge SOP asset.
// This will be our "ConnectedAssetId".
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), -1,
"SOP/merge", nullptr, true, &ConnectedAssetId ), false );
// Add the merge node's parent OBJ to the created nodes
OutCreatedNodeIds.AddUnique( FHoudiniEngineUtils::HapiGetParentNodeId( ConnectedAssetId ) );
}
for ( int32 InputIdx = 0; InputIdx < OutlinerMeshArray.Num(); ++InputIdx )
{
auto & OutlinerMesh = OutlinerMeshArray[ InputIdx ];
bool bInputCreated = false;
if ( OutlinerMesh.StaticMesh && !OutlinerMesh.StaticMesh->IsPendingKill() )
{
// Creating an Input Node for Mesh Data
bInputCreated = HapiCreateInputNodeForStaticMesh(
OutlinerMesh.StaticMesh,
OutlinerMesh.AssetId,
OutCreatedNodeIds,
OutlinerMesh.StaticMeshComponent,
ExportAllLODs, ExportSockets );
}
else if ( OutlinerMesh.SplineComponent && !OutlinerMesh.SplineComponent->IsPendingKill() )
{
// Creating an input node for spline data
bInputCreated = HapiCreateInputNodeForSpline(
ConnectedAssetId,
OutlinerMesh.SplineComponent,
OutlinerMesh.AssetId,
OutlinerMesh,
SplineResolution );
OutCreatedNodeIds.AddUnique( FHoudiniEngineUtils::HapiGetParentNodeId( OutlinerMesh.AssetId ) );
}
if ( !bInputCreated )
{
OutlinerMesh.AssetId = -1;
continue;
}
if ( UseMerge )
{
// Now we can connect the input node to the merge node.
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput(
FHoudiniEngine::Get().GetSession(), ConnectedAssetId, InputIdx,
OutlinerMesh.AssetId, 0), false );
}
else
{
// We only have one input, use its Id as our "ConnectedAssetId"
ConnectedAssetId = OutlinerMesh.AssetId;
}
// Updating the Transform
HAPI_TransformEuler HapiTransform;
FHoudiniApi::TransformEuler_Init(&HapiTransform);
//FMemory::Memzero< HAPI_TransformEuler >( HapiTransform );
FHoudiniEngineUtils::TranslateUnrealTransform( OutlinerMesh.ComponentTransform, HapiTransform );
// Set it on the input mesh's parent OBJ node
HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId( OutlinerMesh.AssetId );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetObjectTransform(
FHoudiniEngine::Get().GetSession(), ParentId, &HapiTransform ), false );
}
#endif
return true;
}
bool
FHoudiniEngineUtils::HapiCreateInputNodeForObjects(
HAPI_NodeId HostAssetId, TArray<UObject *>& InputObjects, const TArray< FTransform >& InputTransforms,
HAPI_NodeId & ConnectedAssetId, TArray< HAPI_NodeId >& OutCreatedNodeIds,
const bool& bExportSkeleton, const bool& bExportAllLODs /* = false */, const bool& bExportSockets /* = false */)
{
#if WITH_EDITOR
if ( InputObjects.Num() <= 0 )
return true;
bool UseMergeNode = InputObjects.Num() > 1;
if ( UseMergeNode )
{
// If we have more than one input mesh, create a merge SOP asset. This will be our "ConnectedAssetId".
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), -1,
"SOP/merge", nullptr, true, &ConnectedAssetId ), false );
}
for ( int32 InputIdx = 0; InputIdx < InputObjects.Num(); ++InputIdx )
{
HAPI_NodeId MeshAssetNodeId = -1;
FTransform InputTransform = FTransform::Identity;
if ( InputTransforms.IsValidIndex( InputIdx ) )
InputTransform = InputTransforms[ InputIdx ];
UStaticMesh* InputStaticMesh = Cast< UStaticMesh >( InputObjects[InputIdx] );
USkeletalMesh* InputSkeletalMesh = Cast< USkeletalMesh >( InputObjects[InputIdx] );
if ( InputStaticMesh && !InputStaticMesh->IsPendingKill() )
{
// Creating an Input Node for Static Mesh Data
if ( !HapiCreateInputNodeForStaticMesh( InputStaticMesh, MeshAssetNodeId, OutCreatedNodeIds, nullptr, bExportAllLODs, bExportSockets ) )
{
HOUDINI_LOG_WARNING( TEXT( "Error creating input index %d on %d" ), InputIdx, ConnectedAssetId );
}
}
else if ( InputSkeletalMesh && !InputSkeletalMesh->IsPendingKill() )
{
// Creating an Input Node for Skeletal Mesh Data
if ( !HapiCreateInputNodeForSkeletalMesh( ConnectedAssetId, InputSkeletalMesh, MeshAssetNodeId, OutCreatedNodeIds, bExportSkeleton ) )
{
HOUDINI_LOG_WARNING( TEXT( "Error creating input index %d on %d" ), InputIdx, ConnectedAssetId );
}
}
if ( MeshAssetNodeId < 0 )
continue;
if ( UseMergeNode )
{
// Now we can connect the input node to the merge node.
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput(
FHoudiniEngine::Get().GetSession(), ConnectedAssetId, InputIdx,
MeshAssetNodeId, 0), false );
}
else
{
// We only have one input, use the MeshNodeId as our "ConnectedAssetId".
ConnectedAssetId = MeshAssetNodeId;
}
// If the Geometry Input has a Transform offset
if ( !InputTransform.Equals( FTransform::Identity ) )
{
// Updating the Transform
HAPI_TransformEuler HapiTransform;
FHoudiniApi::TransformEuler_Init(&HapiTransform);
//FMemory::Memzero< HAPI_TransformEuler >( HapiTransform );
FHoudiniEngineUtils::TranslateUnrealTransform( InputTransform, HapiTransform );
HAPI_NodeInfo LocalAssetNodeInfo;
FHoudiniApi::NodeInfo_Init(&LocalAssetNodeInfo);
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo(
FHoudiniEngine::Get().GetSession(), MeshAssetNodeId, &LocalAssetNodeInfo ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetObjectTransform(
FHoudiniEngine::Get().GetSession(), LocalAssetNodeInfo.parentId, &HapiTransform ), false );
}
}
#endif
return true;
}
bool
FHoudiniEngineUtils::HapiCreateInputNodeForSkeletalMesh(
HAPI_NodeId HostAssetId, USkeletalMesh * SkeletalMesh,
HAPI_NodeId & ConnectedAssetId, TArray< HAPI_NodeId >& OutCreatedNodeIds,
const bool& bExportSkeleton )
{
#if WITH_EDITOR
// If we don't have a skeletal mesh there's nothing to do.
if ( !SkeletalMesh || SkeletalMesh->IsPendingKill() )
return false;
// Check if connected asset id is valid, if it is not, we need to create an input asset.
if ( ConnectedAssetId < 0 )
{
HAPI_NodeId AssetId = -1;
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode(
FHoudiniEngine::Get().GetSession(), &AssetId, nullptr ), false );
// Check if we have a valid id for this new input asset.
if ( !FHoudiniEngineUtils::IsHoudiniNodeValid( AssetId ) )
return false;
// We now have a valid id.
ConnectedAssetId = AssetId;
// Get the input's parent OBJ node
HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId( ConnectedAssetId );
OutCreatedNodeIds.AddUnique( ParentId );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode(
FHoudiniEngine::Get().GetSession(), AssetId, nullptr ), false );
}
// Get runtime settings.
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
check( HoudiniRuntimeSettings );
float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION;
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
int32 GeneratedLightMapResolution = 32;
if ( HoudiniRuntimeSettings )
{
GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor;
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution;
}
// Grab base LOD level.
const FSkeletalMeshModel* SkelMeshResource = SkeletalMesh->GetImportedModel();
if ( !SkelMeshResource )
return false;
const FSkeletalMeshLODModel& SourceModel = SkelMeshResource->LODModels[0];
const int32 VertexCount = SourceModel.GetNumNonClothingVertices();
// Verify the integrity of the mesh.
if ( VertexCount <= 0 )
return false;
// Extract the vertices buffer (this also contains normals, uvs, colors...)
TArray<FSoftSkinVertex> SoftSkinVertices;
SourceModel.GetNonClothVertices( SoftSkinVertices );
if ( SoftSkinVertices.Num() != VertexCount )
return false;
// Extract the indices
TArray<uint32> Indices = SourceModel.IndexBuffer;
int32 FaceCount = Indices.Num() / 3;
// Create part.
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 = Indices.Num();
Part.faceCount = FaceCount;
Part.pointCount = SoftSkinVertices.Num();
Part.type = HAPI_PARTTYPE_MESH;
HAPI_GeoInfo DisplayGeoInfo;
FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetDisplayGeoInfo(
FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &DisplayGeoInfo ), false );
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo(
FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, 0, &Part ), false );
//-------------------------------------------------------------------------
// POSITIONS
//-------------------------------------------------------------------------
// Create point attribute info.
HAPI_AttributeInfo AttributeInfoPoint;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint );
AttributeInfoPoint.count = SoftSkinVertices.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(), DisplayGeoInfo.nodeId, 0,
HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint ), false );
// Convert vertices from the skeletal mesh to a float array
TArray< float > SkelMeshVertices;
SkelMeshVertices.SetNumZeroed( SoftSkinVertices.Num() * 3 );
for ( int32 VertexIdx = 0; VertexIdx < SoftSkinVertices.Num(); ++VertexIdx )
{
// Grab vertex at this index.
const FVector & PositionVector = SoftSkinVertices[ VertexIdx ].Position;
if ( ImportAxis == HRSAI_Unreal )
{
SkelMeshVertices[ VertexIdx * 3 + 0 ] = PositionVector.X / GeneratedGeometryScaleFactor;
SkelMeshVertices[ VertexIdx * 3 + 1 ] = PositionVector.Z / GeneratedGeometryScaleFactor;
SkelMeshVertices[ VertexIdx * 3 + 2 ] = PositionVector.Y / GeneratedGeometryScaleFactor;
}
else
{
SkelMeshVertices[ VertexIdx * 3 + 0 ] = PositionVector.X / GeneratedGeometryScaleFactor;
SkelMeshVertices[ VertexIdx * 3 + 1 ] = PositionVector.Y / GeneratedGeometryScaleFactor;
SkelMeshVertices[ VertexIdx * 3 + 2 ] = PositionVector.Z / GeneratedGeometryScaleFactor;
}
}
// Now that we have raw positions, we can upload them for our attribute.
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId,
0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint,
SkelMeshVertices.GetData(), 0,
AttributeInfoPoint.count ), false );
//-------------------------------------------------------------------------
// UVS
//-------------------------------------------------------------------------
// See if we have texture coordinates to upload.
for ( uint32 MeshTexCoordIdx = 0; MeshTexCoordIdx < SourceModel.NumTexCoords; ++MeshTexCoordIdx )
{
TArray< FVector > MeshUVs;
MeshUVs.SetNumUninitialized( Indices.Num() );
// Transfer UV data.
for ( int32 UVIdx = 0; UVIdx < Indices.Num(); ++UVIdx )
{
// Grab uv for this coordSet at this index.
const FVector2D & UV = SoftSkinVertices[ Indices[UVIdx] ].UVs[ MeshTexCoordIdx ];
MeshUVs[ UVIdx ] = FVector(UV.X, 1.0 - UV.Y, 0);
}
if ( ImportAxis == HRSAI_Unreal )
{
// We need to re-index UVs due to swapped indices in the faces (due to winding order differences)
for ( int32 WedgeIdx = 0; WedgeIdx < Indices.Num(); WedgeIdx += 3 )
{
// Swap second and third values for the vertices of the face
MeshUVs.SwapMemory( WedgeIdx + 1, WedgeIdx + 2 );
}
}
// Construct attribute name for this index.
FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV;
if ( MeshTexCoordIdx > 0 )
UVAttributeName += FString::Printf(TEXT("%d"), MeshTexCoordIdx + 1);
// Create attribute for UVs
HAPI_AttributeInfo AttributeInfoUVS;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoUVS);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoUVS );
AttributeInfoUVS.count = MeshUVs.Num();
AttributeInfoUVS.tupleSize = 3;
AttributeInfoUVS.exists = true;
AttributeInfoUVS.owner = HAPI_ATTROWNER_VERTEX;
AttributeInfoUVS.storage = HAPI_STORAGETYPE_FLOAT;
AttributeInfoUVS.originalOwner = HAPI_ATTROWNER_INVALID;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId,
0, TCHAR_TO_ANSI( *UVAttributeName ), &AttributeInfoUVS), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
DisplayGeoInfo.nodeId, 0, TCHAR_TO_ANSI( *UVAttributeName ), &AttributeInfoUVS,
(const float *) MeshUVs.GetData(), 0, AttributeInfoUVS.count ), false );
}
//-------------------------------------------------------------------------
// NORMALS
//-------------------------------------------------------------------------
TArray< FVector > MeshNormals;
MeshNormals.SetNumUninitialized( Indices.Num() );
// Transfer Normal data.
for (int32 NormalIdx = 0; NormalIdx < Indices.Num(); ++NormalIdx)
{
FPackedNormal PackedNormal = SoftSkinVertices[Indices[NormalIdx]].TangentZ;
MeshNormals[NormalIdx] = PackedNormal.ToFVector();
// Doesnt work on MacOS ...
//MeshNormals[ NormalIdx ] = FVector( SoftSkinVertices[ Indices[NormalIdx] ].TangentZ.Vector. );
}
if ( ImportAxis == HRSAI_Unreal )
{
// We need to re-index normals due to swapped indices on the faces (due to winding differences).
for ( int32 WedgeIdx = 0; WedgeIdx < Indices.Num(); WedgeIdx += 3 )
{
// Swap second and third values for the vertices of the face
MeshNormals.SwapMemory( WedgeIdx + 1, WedgeIdx + 2 );
}
// Also swap the normal's Y and Z
for ( int32 WedgeIdx = 0; WedgeIdx < Indices.Num(); ++WedgeIdx )
Swap( MeshNormals[ WedgeIdx ].Y, MeshNormals[ WedgeIdx ].Z );
}
// Create attribute for normals.
HAPI_AttributeInfo AttributeInfoNormals;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoNormals);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoNormals );
AttributeInfoNormals.count = MeshNormals.Num();
AttributeInfoNormals.tupleSize = 3;
AttributeInfoNormals.exists = true;
AttributeInfoNormals.owner = HAPI_ATTROWNER_VERTEX;
AttributeInfoNormals.storage = HAPI_STORAGETYPE_FLOAT;
AttributeInfoNormals.originalOwner = HAPI_ATTROWNER_INVALID;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId,
0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoNormals ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoNormals,
(const float *)MeshNormals.GetData(),
0, AttributeInfoNormals.count ), false );
//-------------------------------------------------------------------------
// Vertex Colors
//-------------------------------------------------------------------------
TArray< FLinearColor > MeshColors;
MeshColors.SetNumUninitialized( Indices.Num() );
// Transfer Color data.
for (int32 ColorIdx = 0; ColorIdx < Indices.Num(); ++ColorIdx)
{
MeshColors[ ColorIdx ] = SoftSkinVertices[ Indices[ColorIdx] ].Color.ReinterpretAsLinear();
}
if ( ImportAxis == HRSAI_Unreal )
{
// We need to re-index colors due to swapped indices on the faces (due to winding differences).
for ( int32 WedgeIdx = 0; WedgeIdx < Indices.Num(); WedgeIdx += 3 )
{
// Swap second and third values for the vertices of the face
MeshColors.SwapMemory(WedgeIdx + 1, WedgeIdx + 2);
}
}
// Create attribute for colors.
HAPI_AttributeInfo AttributeInfoColors;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoColors);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoColors );
AttributeInfoColors.count = MeshColors.Num();
AttributeInfoColors.tupleSize = 4;
AttributeInfoColors.exists = true;
AttributeInfoColors.owner = HAPI_ATTROWNER_VERTEX;
AttributeInfoColors.storage = HAPI_STORAGETYPE_FLOAT;
AttributeInfoColors.originalOwner = HAPI_ATTROWNER_INVALID;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId,
0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoColors), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoColors,
(const float *)MeshColors.GetData(), 0, AttributeInfoColors.count ), false );
//-------------------------------------------------------------------------
// Indices
//-------------------------------------------------------------------------
// Extract indices from static mesh.
TArray< int32 > MeshIndices;
MeshIndices.SetNumUninitialized( Indices.Num() );
if ( ImportAxis == HRSAI_Unreal )
{
// We have to swap indices to fix the winding order.
for ( int32 IndexIdx = 0; IndexIdx < Indices.Num(); IndexIdx += 3 )
{
MeshIndices[ IndexIdx + 0 ] = Indices[ IndexIdx + 0 ];
MeshIndices[ IndexIdx + 1 ] = Indices[ IndexIdx + 2 ];
MeshIndices[ IndexIdx + 2 ] = Indices[ IndexIdx + 1 ];
}
}
else
{
// Direct copy, no need to change the winding order
MeshIndices = TArray< int32 >( Indices );
}
// We can now set the vertex list.
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetVertexList(
FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId,
0, MeshIndices.GetData(), 0, MeshIndices.Num() ), false );
// We need to generate the array of face counts.
TArray< int32 > MeshFaceCounts;
MeshFaceCounts.Init( 3, Part.faceCount );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetFaceCounts(
FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId,
0, MeshFaceCounts.GetData(), 0, MeshFaceCounts.Num() ), false );
//-------------------------------------------------------------------------
// Face Materials
//-------------------------------------------------------------------------
TArray<int32> FaceMaterialIds;
FaceMaterialIds.SetNumUninitialized( FaceCount );
int32 FaceIdx = 0;
int32 SectionCount = SourceModel.NumNonClothingSections();
for (int32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
{
const FSkelMeshSection& Section = SourceModel.Sections[SectionIndex];
// Copy the material sections for all the faces of the current section
int32 CurrentMatIndex = Section.MaterialIndex;
for (int32 TriangleIndex = 0; TriangleIndex < (int32)Section.NumTriangles; ++TriangleIndex)
{
FaceMaterialIds[FaceIdx++] = CurrentMatIndex;
}
}
// Create an array of Material Interfaces
TArray< UMaterialInterface * > MaterialInterfaces;
MaterialInterfaces.SetNum( SkeletalMesh->Materials.Num() );
for( int32 MatIdx = 0; MatIdx < SkeletalMesh->Materials.Num(); MatIdx++ )
MaterialInterfaces[MatIdx] = SkeletalMesh->Materials[ MatIdx ].MaterialInterface;
// Create list of materials, one for each face.
TArray< char * > MeshFaceMaterials;
FHoudiniEngineUtils::CreateFaceMaterialArray(
MaterialInterfaces, FaceMaterialIds, MeshFaceMaterials );
// Get name of attribute used for marshalling materials.
std::string MarshallingAttributeName = HAPI_UNREAL_ATTRIB_MATERIAL;
if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeMaterial.IsEmpty() )
FHoudiniEngineUtils::ConvertUnrealString( HoudiniRuntimeSettings->MarshallingAttributeMaterial, MarshallingAttributeName );
// Create attribute for materials.
HAPI_AttributeInfo AttributeInfoMaterial;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoMaterial );
AttributeInfoMaterial.count = FaceMaterialIds.Num();
AttributeInfoMaterial.tupleSize = 1;
AttributeInfoMaterial.exists = true;
AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM;
AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING;
AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID;
bool bAttributeError = false;
if ( FHoudiniApi::AddAttribute( FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, 0,
MarshallingAttributeName.c_str(), &AttributeInfoMaterial ) != HAPI_RESULT_SUCCESS )
{
bAttributeError = true;
}
if ( FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(),
DisplayGeoInfo.nodeId, 0, MarshallingAttributeName.c_str(), &AttributeInfoMaterial,
(const char **) MeshFaceMaterials.GetData(), 0,
MeshFaceMaterials.Num() ) != HAPI_RESULT_SUCCESS )
{
bAttributeError = true;
}
// Delete material names.
FHoudiniEngineUtils::DeleteFaceMaterialArray( MeshFaceMaterials );
if ( bAttributeError )
{
check( 0 );
return false;
}
//-------------------------------------------------------------------------
// Mesh name
//-------------------------------------------------------------------------
if ( !HoudiniRuntimeSettings->MarshallingAttributeInputMeshName.IsEmpty() )
{
// Create primitive attribute with mesh asset path
const FString MeshAssetPath = SkeletalMesh->GetPathName();
std::string MeshAssetPathCStr = TCHAR_TO_ANSI( *MeshAssetPath );
const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str();
TArray<const char*> PrimitiveAttrs;
PrimitiveAttrs.AddUninitialized( Part.faceCount );
for ( int32 Ix = 0; Ix < Part.faceCount; ++Ix )
{
PrimitiveAttrs[ Ix ] = MeshAssetPathRaw;
}
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeInputMeshName, MarshallingAttributeName );
HAPI_AttributeInfo AttributeInfo;
FHoudiniApi::AttributeInfo_Init(&AttributeInfo);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(), DisplayGeoInfo.nodeId,
0, MarshallingAttributeName.c_str(), &AttributeInfo ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(),
DisplayGeoInfo.nodeId, 0, MarshallingAttributeName.c_str(), &AttributeInfo,
PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num() ), false );
}
//-------------------------------------------------------------------------
// Mesh asset path
//-------------------------------------------------------------------------
if( !HoudiniRuntimeSettings->MarshallingAttributeInputSourceFile.IsEmpty() )
{
// Create primitive attribute with mesh asset path
FString Filename;
if( UAssetImportData* ImportData = SkeletalMesh->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<const char*> PrimitiveAttrs;
PrimitiveAttrs.AddUninitialized( Part.faceCount );
for( int32 Ix = 0; Ix < Part.faceCount; ++Ix )
{
PrimitiveAttrs[ Ix ] = FilenameCStrRaw;
}
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeInputSourceFile, MarshallingAttributeName );
HAPI_AttributeInfo AttributeInfo;
FHoudiniApi::AttributeInfo_Init(&AttributeInfo);
//FMemory::Memzero< HAPI_AttributeInfo >( 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(), DisplayGeoInfo.nodeId,
0, MarshallingAttributeName.c_str(), &AttributeInfo ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(),
DisplayGeoInfo.nodeId, 0, MarshallingAttributeName.c_str(), &AttributeInfo,
PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num() ), false );
}
}
// Commit the geo before doing the skeleton.
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(
FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId), false);
if ( bExportSkeleton )
{
// Export the Skeleton!
HAPI_NodeInfo NodeInfo;
FHoudiniApi::NodeInfo_Init(&NodeInfo);
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo(
FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, &NodeInfo), false );
FHoudiniEngineUtils::HapiCreateSkeletonFromData( HostAssetId, SkeletalMesh, NodeInfo, OutCreatedNodeIds );
// Commit the geo.
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(
FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId ), false );
}
#endif
return true;
}
bool
FHoudiniEngineUtils::HapiCreateSkeletonFromData(
HAPI_NodeId HostAssetId,
USkeletalMesh * SkeletalMesh,
const HAPI_NodeInfo& SkelMeshNodeInfo,
TArray< HAPI_NodeId >& OutCreatedNodeIds )
{
#if WITH_EDITOR
if ( !SkeletalMesh || SkeletalMesh->IsPendingKill() )
return false;
// We need to have an input asset already!
if ( SkelMeshNodeInfo.parentId < 0 || SkelMeshNodeInfo.id < 0 )
return false;
// Get the skeleton from the skeletal mesh
const FReferenceSkeleton& RefSkeleton = SkeletalMesh->RefSkeleton;
int32 BoneCount = RefSkeleton.GetRawBoneNum();
if ( BoneCount <= 0 )
return false;
// Get runtime settings.
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
check( HoudiniRuntimeSettings );
float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION;
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
int32 GeneratedLightMapResolution = 32;
if ( HoudiniRuntimeSettings )
{
GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor;
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution;
}
// First, we need to create an objnet node in the input that will contains the nulls & bones
HAPI_NodeId ObjnetNodeId = -1;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), SkelMeshNodeInfo.parentId, "objnet", "skeleton", true, &ObjnetNodeId ), false );
// We also have to create an object merge node inside the objnet, to attach the skeletal mesh's geometry to the skeleton
HAPI_NodeId GeoNodeId = -1;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), ObjnetNodeId, "geo", "mesh", true, &GeoNodeId ), false );
// For now, we dont export skinning info!!
// TODO: new HAPI functions are needed to properly set the skinning weights on the vertices
/*
///////// DPT: Deactivated skinning export for now
// We're going to need a capture, capture override and deform node after the object merge,
// So we'll create them now.
// Create the bone deform
HAPI_NodeId DeformNodeId = -1;
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), GeoNodeId, "bonedeform", "mesh_deform", true, &DeformNodeId), false);
// We can delete the default file node created by the geometry node,
// This wll set the display flag top the deform node, which is exactly what we want
HAPI_GeoInfo GeoInfo;
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetDisplayGeoInfo(
FHoudiniEngine::Get().GetSession(), GeoNodeId, &GeoInfo), false);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::DeleteNode(
FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId), false);
// Create the capture attribute pack and unpack nodes
HAPI_NodeId CaptureAttrPackNodeId = -1;
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), GeoNodeId, "captureattribpack", "mesh_capture_pack", true, &CaptureAttrPackNodeId ), false);
HAPI_NodeId CaptureAttrUnpackNodeId = -1;
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), GeoNodeId, "captureattribunpack", "mesh_capture_unpack", true, &CaptureAttrUnpackNodeId ), false);
// Create the capture node
HAPI_NodeId CaptureNodeId = -1;
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), GeoNodeId, "capture", "mesh_capture", true, &CaptureNodeId), false);
// Then create an object merge in the geo node...
HAPI_NodeId ObjMergeNodeId = -1;
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), GeoNodeId, "object_merge", "mesh", true, &ObjMergeNodeId), false);
// ... and set it's object path parameter to the skeletal mesh's geometry
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmNodeValue(
FHoudiniEngine::Get().GetSession(), ObjMergeNodeId, "objpath1", SkelMeshNodeInfo.id), false);
// We can now wire all those nodes together
// Oject Merge > Capture > Capture Override > Capture Unpack > Capture Pack > Deform
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput(
FHoudiniEngine::Get().GetSession(), CaptureNodeId, 0, ObjMergeNodeId, 0), false);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput(
FHoudiniEngine::Get().GetSession(), CaptureAttrUnpackNodeId, 0, CaptureNodeId, 0 ), false);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput(
FHoudiniEngine::Get().GetSession(), CaptureAttrPackNodeId, 0, CaptureAttrUnpackNodeId, 0 ), false);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput(
FHoudiniEngine::Get().GetSession(), DeformNodeId, 0, CaptureAttrPackNodeId, 0 ), false);
*/
// Create an object merge in the geo node...
HAPI_NodeId ObjMergeNodeId = -1;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), GeoNodeId, "object_merge", "mesh", true, &ObjMergeNodeId ), false );
// ... and set it's object path parameter to the skeletal mesh's geometry
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmNodeValue(
FHoudiniEngine::Get().GetSession(), ObjMergeNodeId, "objpath1", SkelMeshNodeInfo.id ), false );
// We can delete the default file node created by the geometry node,
// This will set the display flag to the object merge node
HAPI_GeoInfo GeoInfo;
FHoudiniApi::GeoInfo_Init(&GeoInfo);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetDisplayGeoInfo(
FHoudiniEngine::Get().GetSession(), GeoNodeId, &GeoInfo ), false );
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::DeleteNode(
FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId ), false );
// Arrays keeping track of the created nulls / bone node IDs
TArray<HAPI_NodeId> NullNodeIds;
NullNodeIds.Init(-1, BoneCount );
TArray<HAPI_NodeId> BoneNodeIds;
BoneNodeIds.Init(-1, BoneCount );
// String containing all the relative path to the joints cregion for the capture SOP
int32 NumCapture = 0;
FString Capture_Region_Paths;
HAPI_NodeId RootNullNodeId = -1;
for ( int32 BoneIndex = 0; BoneIndex < RefSkeleton.GetRawBoneNum(); ++BoneIndex )
{
// Get the current bone's info
const FMeshBoneInfo& CurrentBone = RefSkeleton.GetRefBoneInfo()[ BoneIndex ];
const FTransform& BoneTransform = RefSkeleton.GetRefBonePose()[ BoneIndex ];
const FString & BoneName = CurrentBone.ExportName;
// Create a joint (null node) for this bone
HAPI_NodeId NullNodeId = -1;
float BoneLength = 0.0f;
{
std::string NameStr;
FHoudiniEngineUtils::ConvertUnrealString( BoneName, NameStr );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), ObjnetNodeId, "null", NameStr.c_str(), true, &NullNodeId ), false );
// Check if we have a valid id for this new input asset.
if ( NullNodeId == -1 )
return false;
// We have a valid node id.
NullNodeIds[ BoneIndex ] = NullNodeId;
// Convert the joint's transform
HAPI_TransformEuler HapiTransform;
FHoudiniApi::TransformEuler_Init(&HapiTransform);
FHoudiniEngineUtils::TranslateUnrealTransform( BoneTransform, HapiTransform );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetObjectTransform(
FHoudiniEngine::Get().GetSession(), NullNodeId, &HapiTransform), false);
// Calc the bone's length
BoneLength = FVector( HapiTransform.position[0], HapiTransform.position[1], HapiTransform.position[2] ).Size();
// We also need to create a cregion node inside the null node
HAPI_NodeId CRegionId = -1;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), NullNodeId, "cregion", "cregion", true, &CRegionId ), false );
/*
///////// DPT: Deactivated skinning export for now
// Set the CRegion to XAxis??
// Set the squatch and csquatch param to (0, 0, 0) for faster capture cook
// (Since we'll be discarding the capture values)
// Add a path to the cregion of the joint to the paths to be set for capture sop
FString NodePathTemp;
if ( !FHoudiniEngineUtils::HapiGetNodePath( CRegionId, CaptureNodeId, NodePathTemp ) )
continue;
Capture_Region_Paths += NodePathTemp + TEXT(" ");
NumCapture++;
*/
}
// If we're the root node, we don't need to creating bones
if ( CurrentBone.ParentIndex < 0 )
{
RootNullNodeId = NullNodeId;
continue;
}
// Get our parent's id
HAPI_NodeId ParentNullNodeId = -1;
ParentNullNodeId = NullNodeIds[ CurrentBone.ParentIndex ];
// Connect the joints
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput(
FHoudiniEngine::Get().GetSession(), NullNodeId, 0, ParentNullNodeId, 0 ), false );
// Now we need to create the bone
// It has to be named by our parents, and looking at the current null node
HAPI_NodeId BoneNodeId = -1;
{
const FString & ParentName = RefSkeleton.GetRefBoneInfo()[ CurrentBone.ParentIndex ].ExportName + TEXT("_bone");
std::string NameStr;
FHoudiniEngineUtils::ConvertUnrealString( ParentName, NameStr );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateNode(
FHoudiniEngine::Get().GetSession(), ObjnetNodeId, "bone", NameStr.c_str(), true, &BoneNodeId), false );
// Check if we have a valid id for this new input asset.
if ( BoneNodeId == -1 )
return false;
// We have a valid node id.
//NullNodeIds[ BoneIndex ] = BoneNodeId;
// We need to change the length, lookatpath attributes
// The bone looks at the current null
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmNodeValue(
FHoudiniEngine::Get().GetSession(), BoneNodeId, "lookatpath", NullNodeId ), false );
// Set the length parameter
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmFloatValue(
FHoudiniEngine::Get().GetSession(), BoneNodeId, "length", 0, BoneLength), false);
}
// Connect the bone to its parent
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput(
FHoudiniEngine::Get().GetSession(), BoneNodeId, 0, ParentNullNodeId, 0 ), false);
}
// Now that the skeleton has been created, we can connect the Geometry node containing the geometry to the skeleton's root
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput(
FHoudiniEngine::Get().GetSession(), GeoNodeId, 0, RootNullNodeId, 0), false);
/*
///////// DPT: Deactivated skinning export for now
// We can also set the all the cregion path to the capture sop extraregions parameter
// Get param id.
HAPI_ParmId ParmId = -1;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName(
FHoudiniEngine::Get().GetSession(), CaptureNodeId, "extraregions", &ParmId ), false );
std::string ConvertedString = TCHAR_TO_UTF8(*Capture_Region_Paths);
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmStringValue(
FHoudiniEngine::Get().GetSession(), CaptureNodeId, ConvertedString.c_str(), ParmId, 0 ) , false );
//-----------------------------------------------------------------------------------------------------------------
// Extract Skinning info on the skeletal mesh
// Grab base LOD level.
const FSkeletalMeshResource* SkelMeshResource = SkeletalMesh->GetImportedResource();
const FStaticLODModel& SourceModel = SkelMeshResource->LODModels[0];
const int32 VertexCount = SourceModel.GetNumNonClothingVertices();
// Extract the vertices buffer (this also contains normals, uvs, colors...)
TArray<FSoftSkinVertex> SoftSkinVertices;
SourceModel.GetNonClothVertices(SoftSkinVertices);
if ( SoftSkinVertices.Num() != VertexCount )
return false;
// Array containing the weight values for each vertex, this will be converted to boneCapture_data attribute
TArray<float> SkinningWeights;
SkinningWeights.SetNum( VertexCount * MAX_TOTAL_INFLUENCES );
// Array containing the index of the bones used for the weight values, this will be converted to boneCapture_index attribute
TArray<int32> SkinningIndexes;
SkinningIndexes.SetNum( VertexCount * MAX_TOTAL_INFLUENCES );
for (int32 BoneIndex = 0; BoneIndex < BoneCount; ++BoneIndex)
{
// Add all the vertices that are weighted to the current skeletal bone to the cluster
// NOTE: the bone influence indices contained in the vertex data are based on a per-chunk
// list of verts. The convert the chunk bone index to the mesh bone index, the chunk's boneMap is needed
int32 VertIndex = 0;
const int32 SectionCount = SourceModel.Sections.Num();
for (int32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
{
const FSkelMeshSection& Section = SourceModel.Sections[SectionIndex];
for (int32 SoftIndex = 0; SoftIndex < Section.SoftVertices.Num(); ++SoftIndex)
{
const FSoftSkinVertex& Vert = Section.SoftVertices[SoftIndex];
//SkinningIndexes[VertIndex].SetNum( MAX_TOTAL_INFLUENCES )
for (int32 InfluenceIndex = 0; InfluenceIndex < MAX_TOTAL_INFLUENCES; ++InfluenceIndex)
{
SkinningIndexes[ VertIndex * MAX_TOTAL_INFLUENCES + InfluenceIndex ] = Section.BoneMap[ Vert.InfluenceBones[ InfluenceIndex ] ];
SkinningWeights[ VertIndex * MAX_TOTAL_INFLUENCES + InfluenceIndex ] = Vert.InfluenceWeights[ InfluenceIndex ] / 255.f;
}
++VertIndex;
}
}
}
// We need to make sure the capture/capture unpack SOP are cooked before doing this
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode(
FHoudiniEngine::Get().GetSession(), CaptureNodeId, nullptr ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode(
FHoudiniEngine::Get().GetSession(), CaptureAttrUnpackNodeId, nullptr), false);
// Convert weight to the boneCapture_data attribute
HAPI_AttributeInfo DataAttrInfo;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo(
FHoudiniEngine::Get().GetSession(), CaptureAttrUnpackNodeId, 0, "boneCapture_data", HAPI_ATTROWNER_POINT, &DataAttrInfo ), false );
DataAttrInfo.tupleSize = MAX_TOTAL_INFLUENCES;
// Get the old values
TArray<float> Array;
Array.SetNum(DataAttrInfo.count * DataAttrInfo.tupleSize);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData(
FHoudiniEngine::Get().GetSession(), CaptureAttrUnpackNodeId, 0, "boneCapture_data", &DataAttrInfo, 0, Array.GetData(), 0, DataAttrInfo.count), false);
// Add the updated attribute
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), SkelMeshNodeInfo.id,//CaptureAttrUnpackNodeId,
0, "boneCapture_data", &DataAttrInfo ), false );
// Set the new values
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(), SkelMeshNodeInfo.id,//CaptureAttrUnpackNodeId,
0, "boneCapture_data", &DataAttrInfo, SkinningWeights.GetData(), 0, VertexCount ), false );
// Convert indexes to the boneCapture_index attribute
HAPI_AttributeInfo IndexAttrInfo;
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo(
FHoudiniEngine::Get().GetSession(), CaptureAttrUnpackNodeId, 0, "boneCapture_index", HAPI_ATTROWNER_POINT, &IndexAttrInfo ), false);
IndexAttrInfo.tupleSize = MAX_TOTAL_INFLUENCES;
// Add the updated attribute to the mesh node
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), SkelMeshNodeInfo.id,//CaptureAttrUnpackNodeId,
0, "boneCapture_index", &IndexAttrInfo), false );
// Set the new values
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData(
FHoudiniEngine::Get().GetSession(), SkelMeshNodeInfo.id,//CaptureAttrUnpackNodeId,
0, "boneCapture_index", &IndexAttrInfo, SkinningIndexes.GetData(), 0, VertexCount ), false);
*/
// Finally, we'll add a detail attribute with the path to the skeleton root node
// This is an OBJ asset, return the path to this geo relative to the asset
FString RootNodePath;
if ( FHoudiniEngineUtils::HapiGetNodePath( RootNullNodeId, HostAssetId, RootNodePath ) )
{
const char * NodePathStr = TCHAR_TO_UTF8(*RootNodePath);
HAPI_AttributeInfo AttributeInfo;
FHoudiniApi::AttributeInfo_Init(&AttributeInfo);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfo );
AttributeInfo.count = 1;
AttributeInfo.tupleSize = 1;
AttributeInfo.exists = true;
AttributeInfo.owner = HAPI_ATTROWNER_DETAIL;
AttributeInfo.storage = HAPI_STORAGETYPE_STRING;
AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), SkelMeshNodeInfo.id,
0, "unreal_skeleton_root_node", &AttributeInfo), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(), SkelMeshNodeInfo.id,
0, "unreal_skeleton_root_node", &AttributeInfo, (const char**)&NodePathStr, 0, 1 ), false );
}
#endif
return true;
}
bool
FHoudiniEngineUtils::HapiDisconnectAsset( HAPI_NodeId HostAssetId, int32 InputIndex )
{
#if WITH_EDITOR
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::DisconnectNodeInput(
FHoudiniEngine::Get().GetSession(), HostAssetId, InputIndex ), false );
#endif // WITH_EDITOR
return true;
}
bool
FHoudiniEngineUtils::HapiSetAssetTransform( HAPI_NodeId AssetId, const FTransform & Transform )
{
if (!FHoudiniEngineUtils::IsValidNodeId(AssetId))
return false;
// Translate Unreal transform to HAPI Euler one.
HAPI_TransformEuler TransformEuler;
FHoudiniApi::TransformEuler_Init(&TransformEuler);
//FMemory::Memzero< HAPI_TransformEuler >( TransformEuler );
FHoudiniEngineUtils::TranslateUnrealTransform( Transform, TransformEuler );
// Get the NodeInfo
HAPI_NodeInfo LocalAssetNodeInfo;
FHoudiniApi::NodeInfo_Init(&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;
}
bool FHoudiniEngineUtils::CreateStaticMeshesFromHoudiniAsset(
HAPI_NodeId AssetId,
FHoudiniCookParams& HoudiniCookParams,
bool ForceRebuildStaticMesh, bool ForceRecookAll,
const TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesIn,
TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesOut,
FTransform & ComponentTransform )
{
#if WITH_EDITOR
/*
// When called via commandlet, this might be perfectly valid
if ( !FHoudiniEngineUtils::IsHoudiniAssetValid( AssetId ) || !HoudiniCookParams.HoudiniAsset )
return false;
*/
// Make sure rendering is done - so we are not changing data being used by collision drawing.
FlushRenderingCommands();
// Get runtime settings.
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
check( HoudiniRuntimeSettings );
float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION;
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
// Attribute marshalling names.
std::string MarshallingAttributeNameLightmapResolution = HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION;
std::string MarshallingAttributeNameMaterial = HAPI_UNREAL_ATTRIB_MATERIAL;
std::string MarshallingAttributeNameMaterialFallback = HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK;
std::string MarshallingAttributeNameMaterialInstance = HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE;
std::string MarshallingAttributeNameFaceSmoothingMask = HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK;
// Group name prefix used for collision geometry generation.
FString CollisionGroupNamePrefix = TEXT("collision_geo");
FString RenderedCollisionGroupNamePrefix = TEXT("rendered_collision_geo");
FString UCXCollisionGroupNamePrefix = TEXT("collision_geo_ucx");
FString UCXRenderedCollisionGroupNamePrefix = TEXT("rendered_collision_geo_ucx");
FString SimpleCollisionGroupNamePrefix = TEXT("collision_geo_simple");
FString SimpleRenderedCollisionGroupNamePrefix = TEXT("rendered_collision_geo_simple");
// Add runtime setting for those?
FString LodGroupNamePrefix = TEXT("lod");
FString SocketGroupNamePrefix = TEXT("socket");
if ( HoudiniRuntimeSettings )
{
GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor;
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
if ( !HoudiniRuntimeSettings->MarshallingAttributeLightmapResolution.IsEmpty() )
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeLightmapResolution, MarshallingAttributeNameLightmapResolution );
if ( !HoudiniRuntimeSettings->MarshallingAttributeMaterial.IsEmpty() )
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeMaterial, MarshallingAttributeNameMaterial );
if ( !HoudiniRuntimeSettings->MarshallingAttributeFaceSmoothingMask.IsEmpty() )
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeFaceSmoothingMask, MarshallingAttributeNameFaceSmoothingMask );
if ( !HoudiniRuntimeSettings->CollisionGroupNamePrefix.IsEmpty() )
CollisionGroupNamePrefix = HoudiniRuntimeSettings->CollisionGroupNamePrefix;
if ( !HoudiniRuntimeSettings->RenderedCollisionGroupNamePrefix.IsEmpty() )
RenderedCollisionGroupNamePrefix = HoudiniRuntimeSettings->RenderedCollisionGroupNamePrefix;
if ( !HoudiniRuntimeSettings->UCXCollisionGroupNamePrefix.IsEmpty() )
UCXCollisionGroupNamePrefix = HoudiniRuntimeSettings->UCXCollisionGroupNamePrefix;
if ( !HoudiniRuntimeSettings->UCXRenderedCollisionGroupNamePrefix.IsEmpty() )
UCXRenderedCollisionGroupNamePrefix = HoudiniRuntimeSettings->UCXRenderedCollisionGroupNamePrefix;
if ( !HoudiniRuntimeSettings->SimpleCollisionGroupNamePrefix.IsEmpty() )
SimpleCollisionGroupNamePrefix = HoudiniRuntimeSettings->SimpleCollisionGroupNamePrefix;
if ( !HoudiniRuntimeSettings->SimpleRenderedCollisionGroupNamePrefix.IsEmpty() )
SimpleRenderedCollisionGroupNamePrefix = HoudiniRuntimeSettings->SimpleRenderedCollisionGroupNamePrefix;
}
// Get platform manager LOD specific information.
ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform();
check( CurrentPlatform );
FStaticMeshLODGroup LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup( NAME_None );
int32 DefaultNumLODs = LODGroup.GetDefaultNumLODs();
// Get the AssetInfo
HAPI_AssetInfo AssetInfo;
FHoudiniApi::AssetInfo_Init(&AssetInfo);
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAssetInfo(
FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ), false );
// Retrieve asset transform.
FTransform AssetUnrealTransform;
if ( !FHoudiniEngineUtils::HapiGetAssetTransform( AssetId, AssetUnrealTransform ) )
return false;
ComponentTransform = AssetUnrealTransform;
// Retrieve information about each object contained within our asset.
TArray< HAPI_ObjectInfo > ObjectInfos;
if ( !FHoudiniEngineUtils::HapiGetObjectInfos( AssetId, ObjectInfos ) )
return false;
const int32 ObjectCount = ObjectInfos.Num();
// Retrieve transforms for each object in this asset.
TArray< HAPI_Transform > ObjectTransforms;
if ( !FHoudiniEngineUtils::HapiGetObjectTransforms( AssetId, ObjectTransforms ) )
return false;
// Retrieve all used unique material ids.
TSet< HAPI_NodeId > UniqueMaterialIds;
TSet< HAPI_NodeId > UniqueInstancerMaterialIds;
TMap< FHoudiniGeoPartObject, HAPI_NodeId > InstancerMaterialMap;
FHoudiniEngineUtils::ExtractUniqueMaterialIds(
AssetInfo, UniqueMaterialIds, UniqueInstancerMaterialIds, InstancerMaterialMap );
// Create All the materials found on the asset
TMap< FString, UMaterialInterface * > Materials;
FHoudiniEngineMaterialUtils::HapiCreateMaterials(
AssetId, HoudiniCookParams, AssetInfo, UniqueMaterialIds,
UniqueInstancerMaterialIds, Materials, ForceRecookAll );
// Update all material assignments
HoudiniCookParams.HoudiniCookManager->ClearAssignmentMaterials();
for( const auto& AssPair : Materials )
{
HoudiniCookParams.HoudiniCookManager->AddAssignmentMaterial( AssPair.Key, AssPair.Value );
}
// Iterate through all objects.
for ( int32 ObjectIdx = 0; ObjectIdx < ObjectInfos.Num(); ++ObjectIdx )
{
// Retrieve object at this index.
const HAPI_ObjectInfo & ObjectInfo = ObjectInfos[ ObjectIdx ];
// Retrieve object name.
FString ObjectName = TEXT( "" );
FHoudiniEngineString HoudiniEngineString( ObjectInfo.nameSH );
HoudiniEngineString.ToFString( ObjectName );
// Get transformation for this object.
const HAPI_Transform & ObjectTransform = ObjectTransforms[ ObjectIdx ];
FTransform TransformMatrix;
FHoudiniEngineUtils::TranslateHapiTransform( ObjectTransform, TransformMatrix );
// Handle Editable nodes
// First, get the number of editable nodes
int32 EditableNodeCount = 0;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ComposeChildNodeList(
FHoudiniEngine::Get().GetSession(), ObjectInfo.nodeId,
HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, true, &EditableNodeCount ), false );
if ( EditableNodeCount > 0 )
{
TArray< HAPI_NodeId > EditableNodeIds;
EditableNodeIds.SetNumUninitialized( EditableNodeCount );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetComposedChildNodeList(
FHoudiniEngine::Get().GetSession(), AssetId,
EditableNodeIds.GetData(), EditableNodeCount ), false );
for ( int nEditable = 0; nEditable < EditableNodeCount; nEditable++ )
{
HAPI_GeoInfo CurrentEditableGeoInfo;
FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo);
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetGeoInfo(
FHoudiniEngine::Get().GetSession(), EditableNodeIds[ nEditable ], &CurrentEditableGeoInfo ), false );
// do not process the main display geo
if ( CurrentEditableGeoInfo.isDisplayGeo )
continue;
// We only handle editable curves
if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE )
continue;
// Add the editable curve to the output array
FHoudiniGeoPartObject HoudiniGeoPartObject(
TransformMatrix, ObjectName, ObjectName, AssetId,
ObjectInfo.nodeId, CurrentEditableGeoInfo.nodeId, 0 );
HoudiniGeoPartObject.bIsVisible = ObjectInfo.isVisible;
HoudiniGeoPartObject.bIsInstancer = false;
HoudiniGeoPartObject.bIsCurve = true;
HoudiniGeoPartObject.bIsEditable = CurrentEditableGeoInfo.isEditable;
HoudiniGeoPartObject.bHasGeoChanged = CurrentEditableGeoInfo.hasGeoChanged;
StaticMeshesOut.Add( HoudiniGeoPartObject, nullptr );
}
}
// Get the Display Geo's info
HAPI_GeoInfo GeoInfo;
FHoudiniApi::GeoInfo_Init(&GeoInfo);
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo(
FHoudiniEngine::Get().GetSession(), ObjectInfo.nodeId, &GeoInfo ) )
{
HOUDINI_LOG_MESSAGE(
TEXT("Creating Static Meshes: Object [%d %s] unable to retrieve GeoInfo, - skipping."),
ObjectInfo.nodeId, *ObjectName );
continue;
}
// Get object / geo group memberships for primitives.
TArray< FString > ObjectGeoGroupNames;
if( ! FHoudiniEngineUtils::HapiGetGroupNames(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, 0, HAPI_GROUPTYPE_PRIM, ObjectGeoGroupNames, false ) )
{
HOUDINI_LOG_MESSAGE( TEXT( "Creating Static Meshes: Object [%d %s] non-fatal error reading group names" ),
ObjectInfo.nodeId, *ObjectName );
}
// Prepare the object that will store UCX/UBX/USP Collision geo
FKAggregateGeom AggregateCollisionGeo;
bool bHasAggregateGeometryCollision = false;
// Prepare the object that will store the mesh sockets and their names
TArray< FTransform > AllSockets;
TArray< FString > AllSocketsNames;
TArray< FString > AllSocketsActors;
TArray< FString > AllSocketsTags;
for ( int32 PartIdx = 0; PartIdx < GeoInfo.partCount; ++PartIdx )
{
// Get part information.
HAPI_PartInfo PartInfo;
FHoudiniApi::PartInfo_Init(&PartInfo);
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo(
FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId, PartIdx, &PartInfo ) )
{
// Error retrieving part info.
HOUDINI_LOG_MESSAGE(
TEXT( "Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d] unable to retrieve PartInfo - skipping." ),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx );
continue;
}
// Retrieve part name.
FString PartName = TEXT("");
FHoudiniEngineString HoudiniEngineStringPartName( PartInfo.nameSH );
HoudiniEngineStringPartName.ToFString( PartName );
// Unsupported/Invalid part
if( PartInfo.type == HAPI_PARTTYPE_INVALID )
continue;
// Create geo part object identifier.
FHoudiniGeoPartObject HoudiniGeoPartObject(
TransformMatrix, ObjectName, PartName, AssetId,
ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id );
HoudiniGeoPartObject.bIsVisible = ObjectInfo.isVisible && !PartInfo.isInstanced;
HoudiniGeoPartObject.bIsInstancer = ObjectInfo.isInstancer;
HoudiniGeoPartObject.bIsCurve = ( PartInfo.type == HAPI_PARTTYPE_CURVE );
HoudiniGeoPartObject.bIsEditable = GeoInfo.isEditable;
HoudiniGeoPartObject.bHasGeoChanged = GeoInfo.hasGeoChanged;
HoudiniGeoPartObject.bIsBox = ( PartInfo.type == HAPI_PARTTYPE_BOX );
HoudiniGeoPartObject.bIsSphere = ( PartInfo.type == HAPI_PARTTYPE_SPHERE );
HoudiniGeoPartObject.bIsVolume = ( PartInfo.type == HAPI_PARTTYPE_VOLUME );
// See if a custom name for the mesh was assigned via the GeneratedMeshName attribute
HoudiniGeoPartObject.UpdateCustomName();
// See if a custom bake folder override for the mesh was assigned via the "unreal_bake_folder" attribute
TArray< FString > BakeFolderOverrides;
{
HAPI_AttributeInfo AttribBakeFolderOverride;
FHoudiniApi::AttributeInfo_Init(&AttribBakeFolderOverride);
FHoudiniEngineUtils::HapiGetAttributeDataAsString(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id,
HAPI_UNREAL_ATTRIB_BAKE_FOLDER, AttribBakeFolderOverride, BakeFolderOverrides );
if ( BakeFolderOverrides.Num() > 0 )
{
const FString & BakeFolderOverride = BakeFolderOverrides[ 0 ];
if ( !BakeFolderOverride.IsEmpty() )
HoudiniCookParams.BakeFolder = FText::FromString( BakeFolderOverride );
}
}
// Extracting Sockets points on the current part and add them to the list
AddMeshSocketToList( AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, AllSockets, AllSocketsNames, AllSocketsActors, AllSocketsTags, PartInfo.isInstanced );
if ( PartInfo.type == HAPI_PARTTYPE_INSTANCER )
{
// This is a Packed Primitive instancer
HoudiniGeoPartObject.bIsVisible = ObjectInfo.isVisible && !ObjectInfo.isInstanced;
HoudiniGeoPartObject.bIsInstancer = false;
HoudiniGeoPartObject.bIsPackedPrimitiveInstancer = true;
StaticMeshesOut.Add( HoudiniGeoPartObject, nullptr );
continue;
}
else if ( PartInfo.type == HAPI_PARTTYPE_VOLUME )
{
// Volume Data, this is probably a Heightfield
HoudiniGeoPartObject.bIsVisible = ObjectInfo.isVisible && !ObjectInfo.isInstanced;
HoudiniGeoPartObject.bIsInstancer = false;
HoudiniGeoPartObject.bIsPackedPrimitiveInstancer = false;
HoudiniGeoPartObject.bIsVolume = true;
// We need to set the GeoChanged flag to true if we want to force the landscape reimport
HoudiniGeoPartObject.bHasGeoChanged = ( GeoInfo.hasGeoChanged || ForceRebuildStaticMesh || ForceRecookAll );
StaticMeshesOut.Add( HoudiniGeoPartObject, nullptr );
continue;
}
else if ( PartInfo.type == HAPI_PARTTYPE_CURVE )
{
// This is a curve part.
StaticMeshesOut.Add( HoudiniGeoPartObject, nullptr );
continue;
}
else if ( !ObjectInfo.isInstancer && PartInfo.vertexCount <= 0 )
{
// This is not an instancer, but we do not have vertices, so maybe this is a point cloud with attribute override instancing
// If it is, add it to the out list, if not skip it
if ( HoudiniGeoPartObject.IsAttributeInstancer() || HoudiniGeoPartObject.IsAttributeOverrideInstancer() )
{
HoudiniGeoPartObject.bIsInstancer = true;
StaticMeshesOut.Add( HoudiniGeoPartObject, nullptr );
}
continue;
}
// There are no vertices AND no points.
if ( PartInfo.vertexCount <= 0 && PartInfo.pointCount <= 0 )
{
HOUDINI_LOG_MESSAGE(
TEXT( "Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] no points or vertices found - skipping." ),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName );
continue;
}
// This is an instancer with no points.
if ( ObjectInfo.isInstancer && PartInfo.pointCount <= 0 )
{
HOUDINI_LOG_MESSAGE(
TEXT( "Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is instancer but has 0 points - skipping." ),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName );
continue;
}
// Retrieve material information for this geo part.
TArray< HAPI_NodeId > PartFaceMaterialIds;
HAPI_Bool bSingleFaceMaterial = false;
bool bPartHasMaterials = false;
bool bMaterialsChanged = false;
if ( PartInfo.faceCount > 0 )
{
PartFaceMaterialIds.SetNumUninitialized( PartInfo.faceCount );
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialNodeIdsOnFaces(
FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId, PartInfo.id,
&bSingleFaceMaterial, &PartFaceMaterialIds[ 0 ], 0, PartInfo.faceCount ) )
{
// 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, " )
TEXT( "- skipping." ),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName );
continue;
}
// Set flag if we have materials.
TArray< HAPI_NodeId > PartUniqueMaterialIds;
for ( int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx )
PartUniqueMaterialIds.AddUnique( PartFaceMaterialIds[ MaterialIdx ] );
PartUniqueMaterialIds.RemoveSingle( -1 );
bPartHasMaterials = PartUniqueMaterialIds.Num() > 0;
// Set flag if any of the materials have changed.
if ( bPartHasMaterials )
{
for ( int32 MaterialIdx = 0; MaterialIdx < PartUniqueMaterialIds.Num(); ++MaterialIdx )
{
HAPI_MaterialInfo MaterialInfo;
FHoudiniApi::MaterialInfo_Init(&MaterialInfo);
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialInfo(
FHoudiniEngine::Get().GetSession(), PartUniqueMaterialIds[ MaterialIdx ], &MaterialInfo ) )
continue;
if ( MaterialInfo.hasChanged )
{
bMaterialsChanged = true;
break;
}
}
}
}
// We do not create mesh for instancers.
if ( ObjectInfo.isInstancer && PartInfo.pointCount > 0 )
{
// We need to check whether this instancer has a material.
HAPI_NodeId const * FoundInstancerMaterialId = InstancerMaterialMap.Find( HoudiniGeoPartObject );
if ( FoundInstancerMaterialId )
{
HAPI_NodeId InstancerMaterialId = *FoundInstancerMaterialId;
FString InstancerMaterialShopName = TEXT( "" );
if ( InstancerMaterialId > -1 && FHoudiniEngineMaterialUtils::GetUniqueMaterialShopName( AssetId, InstancerMaterialId, InstancerMaterialShopName ) )
{
HoudiniGeoPartObject.bInstancerMaterialAvailable = true;
HoudiniGeoPartObject.InstancerMaterialName = InstancerMaterialShopName;
}
}
// See if we have instancer attribute material present.
{
HAPI_AttributeInfo AttribInstancerAttribMaterials;
FHoudiniApi::AttributeInfo_Init(&AttribInstancerAttribMaterials);
//FMemory::Memset< HAPI_AttributeInfo >( AttribInstancerAttribMaterials, 0 );
TArray< FString > InstancerAttribMaterials;
FHoudiniEngineUtils::HapiGetAttributeDataAsString(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId,
PartInfo.id, MarshallingAttributeNameMaterial.c_str(), AttribInstancerAttribMaterials,
InstancerAttribMaterials );
if ( AttribInstancerAttribMaterials.exists && InstancerAttribMaterials.Num() > 0 )
{
const FString & InstancerAttribMaterialName = InstancerAttribMaterials[ 0 ];
if ( !InstancerAttribMaterialName.IsEmpty() )
{
HoudiniGeoPartObject.bInstancerAttributeMaterialAvailable = true;
HoudiniGeoPartObject.InstancerAttributeMaterialName = InstancerAttribMaterialName;
}
}
}
// Instancer objects have no mesh assigned.
StaticMeshesOut.Add( HoudiniGeoPartObject, nullptr );
continue;
}
// Containers used for raw data extraction.
// Vertex Positions
TArray< float > PartPositions;
HAPI_AttributeInfo AttribInfoPositions;
FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions);
//FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoPositions );
// Vertex Normals
TArray< float > PartNormals;
HAPI_AttributeInfo AttribInfoNormals;
FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals);
// FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoNormals );
// Vertex TangentU
TArray< float > PartTangentU;
HAPI_AttributeInfo AttribInfoTangentU;
FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentU);
//FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoTangentU );
// Vertex TangentV
TArray< float > PartTangentV;
HAPI_AttributeInfo AttribInfoTangentV;
FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentV);
//FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoTangentV );
// Vertex Colors
TArray< float > PartColors;
HAPI_AttributeInfo AttribInfoColors;
FHoudiniApi::AttributeInfo_Init(&AttribInfoColors);
//FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoColors );
// Vertex Alpha values
TArray< float > PartAlphas;
HAPI_AttributeInfo AttribInfoAlpha;
FHoudiniApi::AttributeInfo_Init(&AttribInfoAlpha);
//FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoAlpha );
// UVs
TArray< TArray< float > > PartUVs;
PartUVs.SetNumZeroed( MAX_STATIC_TEXCOORDS );
TArray< HAPI_AttributeInfo > AttribInfoUVs;
AttribInfoUVs.SetNumUninitialized( MAX_STATIC_TEXCOORDS );
for ( int32 Idx = 0; Idx < AttribInfoUVs.Num(); Idx++ )
FHoudiniApi::AttributeInfo_Init(&(AttribInfoUVs[Idx]));
// Material Overrides per face
TArray< FString > PartFaceMaterialAttributeOverrides;
HAPI_AttributeInfo AttribFaceMaterials;
FHoudiniApi::AttributeInfo_Init(&AttribFaceMaterials);
//FMemory::Memzero< HAPI_AttributeInfo >( AttribFaceMaterials );
// Face Smoothing masks
TArray< int32 > PartFaceSmoothingMasks;
HAPI_AttributeInfo AttribInfoFaceSmoothingMasks;
FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceSmoothingMasks);
//FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoFaceSmoothingMasks );
// Lightmap resolution
TArray< int32 > PartLightMapResolutions;
HAPI_AttributeInfo AttribLightmapResolution;
FHoudiniApi::AttributeInfo_Init(&AttribLightmapResolution);
//FMemory::Memzero< HAPI_AttributeInfo >( AttribLightmapResolution );
// Vertex Indices
TArray< int32 > PartVertexList;
PartVertexList.SetNumUninitialized( PartInfo.vertexCount );
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetVertexList(
FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId, PartInfo.id,
&PartVertexList[ 0 ], 0, 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." ),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName );
continue;
}
// Array Storing the GroupNames for the current part
TArray< FString > GroupNames;
if ( !PartInfo.isInstanced )
{
GroupNames = ObjectGeoGroupNames;
}
else
{
// We're a packed primitive, so we want to get the group on the actual
// packed geo, not on the main display geo
if (!FHoudiniEngineUtils::HapiGetGroupNames(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, HAPI_GROUPTYPE_PRIM, GroupNames, true))
GroupNames = ObjectGeoGroupNames;
}
// Sort the Group name array so the LODs are ordered
GroupNames.Sort();
// See if we require splitting.
TArray<FString> SplitGroupNames;
int32 nLODInsertPos = 0;
int32 NumberOfLODs = 0;
for ( int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx )
{
const FString & GroupName = GroupNames[ GeoGroupNameIdx ];
// We're going to order the groups:
// Simple and convex invisible colliders should be created first as they will have to be attached to the visible meshes
// LODs should be created then, ordered by their names
// Visible colliders should be created then, and finally, invisible complex colliders
if ( GroupName.StartsWith( SimpleCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) )
{
// Invisible simple colliders should be treated first
SplitGroupNames.Insert( GroupName, 0 );
nLODInsertPos++;
}
else if ( GroupName.StartsWith( UCXCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) )
{
// Invisible complex colliders should be treated first
SplitGroupNames.Insert( GroupName, 0 );
nLODInsertPos++;
}
else if ( GroupName.StartsWith( LodGroupNamePrefix, ESearchCase::IgnoreCase ) )
{
// LOD meshes should be next
SplitGroupNames.Insert( GroupName, nLODInsertPos++ );
NumberOfLODs++;
}
else if ( GroupName.StartsWith( RenderedCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) )
{
// Visible colliders (simple, convex or complex) should be treated after LODs
SplitGroupNames.Insert( GroupName, nLODInsertPos );
}
else if ( GroupName.StartsWith(CollisionGroupNamePrefix, ESearchCase::IgnoreCase ) )
{
// Invisible complex colliders should be treated last
SplitGroupNames.Add( GroupName );
}
}
bool bRequireSplit = SplitGroupNames.Num() > 0;
TMap< FString, TArray< int32 > > GroupSplitFaces;
TMap< FString, int32 > GroupSplitFaceCounts;
TMap< FString, TArray< int32 > > GroupSplitFaceIndices;
int32 GroupVertexListCount = 0;
static const FString RemainingGroupName = TEXT( HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION );
if ( bRequireSplit )
{
// 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< int32 > AllSplitVertexList;
AllSplitVertexList.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< int32 > AllSplitFaceIndices;
AllSplitFaceIndices.SetNumZeroed( PartFaceMaterialIds.Num() );
// Some of the groups may contain invalid geometry
// Store them here so we can remove them afterwards
TArray< int32 > InvalidGroupNameIndices;
// Extract the vertices/faces for each of the split groups
for ( int32 SplitIdx = 0; SplitIdx < SplitGroupNames.Num(); SplitIdx++ )
{
FString GroupName = SplitGroupNames[ SplitIdx ];
// New vertex list just for this group.
TArray< int32 > GroupVertexList;
TArray< int32 > AllFaceList;
// Extract vertex indices for this split.
GroupVertexListCount = FHoudiniEngineUtils::HapiGetVertexListForGroup(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id, GroupName, PartVertexList, GroupVertexList,
AllSplitVertexList, AllFaceList, AllSplitFaceIndices, PartInfo.isInstanced );
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." ),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, *GroupName );
continue;
}
// If list is not empty, we store it for this group - this will define new mesh.
GroupSplitFaces.Add( GroupName, GroupVertexList );
GroupSplitFaceCounts.Add( GroupName, GroupVertexListCount );
GroupSplitFaceIndices.Add( GroupName, AllFaceList );
}
if ( InvalidGroupNameIndices.Num() > 0 )
{
// Remove all invalid split groups
for (int32 InvalIdx = InvalidGroupNameIndices.Num() - 1; InvalIdx >= 0; InvalIdx--)
{
int32 Index = InvalidGroupNameIndices[ InvalIdx ];
if ( SplitGroupNames[Index].StartsWith(LodGroupNamePrefix, ESearchCase::IgnoreCase) )
NumberOfLODs--;
SplitGroupNames.RemoveAt( Index );
if (Index <= nLODInsertPos && ( nLODInsertPos > 0 ) )
nLODInsertPos--;
}
}
// We also need to figure out / construct vertex list for everything that's not in a split group
TArray< int32 > GroupSplitFacesRemaining;
GroupSplitFacesRemaining.Init( -1, PartVertexList.Num() );
bool bMainSplitGroup = false;
GroupVertexListCount = 0;
TArray< int32 > GroupSplitFaceIndicesRemaining;
for ( int32 SplitVertexIdx = 0; SplitVertexIdx < AllSplitVertexList.Num(); SplitVertexIdx++ )
{
if ( AllSplitVertexList[ SplitVertexIdx ] == 0 )
{
// This is unused index, we need to add it to unused vertex list.
GroupSplitFacesRemaining[ SplitVertexIdx ] = PartVertexList[ SplitVertexIdx ];
bMainSplitGroup = true;
GroupVertexListCount++;
}
}
for ( int32 SplitFaceIdx = 0; SplitFaceIdx < AllSplitFaceIndices.Num(); SplitFaceIdx++ )
{
if ( AllSplitFaceIndices[ SplitFaceIdx ] == 0 )
{
// This is unused face, we need to add it to unused faces list.
GroupSplitFaceIndicesRemaining.Add( SplitFaceIdx );
}
}
// We store the remaining geo vertex list as a special name (main geo)
// and make sure its treated before the collider meshes
if ( bMainSplitGroup )
{
SplitGroupNames.Insert( RemainingGroupName, nLODInsertPos );
GroupSplitFaces.Add( RemainingGroupName, GroupSplitFacesRemaining );
GroupSplitFaceCounts.Add( RemainingGroupName, GroupVertexListCount );
GroupSplitFaceIndices.Add( RemainingGroupName, GroupSplitFaceIndicesRemaining );
}
}
else
{
// No splitting required
SplitGroupNames.Add( RemainingGroupName );
GroupSplitFaces.Add( RemainingGroupName, PartVertexList );
GroupSplitFaceCounts.Add( RemainingGroupName, PartVertexList.Num() );
TArray<int32> AllFaces;
for ( int32 FaceIdx = 0; FaceIdx < PartInfo.faceCount; ++FaceIdx )
AllFaces.Add( FaceIdx );
GroupSplitFaceIndices.Add( RemainingGroupName, AllFaces );
}
// Look for LOD Specific attributes, "lod_screensize" by default
TArray< float > LODScreenSizes;
HAPI_AttributeInfo AttribInfoLODScreenSize;
FHoudiniApi::AttributeInfo_Init(&AttribInfoLODScreenSize);
//FMemory::Memzero< HAPI_AttributeInfo >( AttribInfoLODScreenSize );
FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id,
"lod_screensize", AttribInfoLODScreenSize, LODScreenSizes );
// Keep track of the LOD Index
int32 LodIndex = 0;
int32 LodSplitId = -1;
// 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;
// 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 < SplitGroupNames.Num(); SplitId++ )
{
// Get split group name
const FString & SplitGroupName = SplitGroupNames[ SplitId ];
// Get the vertex indices for this group
TArray< int32 > & SplitGroupVertexList = GroupSplitFaces[ SplitGroupName ];
// Get valid count of vertex indices for this split.
int32 SplitGroupVertexListCount = GroupSplitFaceCounts[ SplitGroupName ];
// Make sure we have a valid vertex count for this split
if (SplitGroupVertexListCount % 3 != 0 || SplitGroupVertexList.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."),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName);
continue;
}
// Get face indices for this split.
TArray< int32 > & SplitGroupFaceIndices = GroupSplitFaceIndices[ SplitGroupName ];
// LOD meshes need to use the same SplitID (as they will be on the same static mesh)
bool IsLOD = SplitGroupName.StartsWith( LodGroupNamePrefix, ESearchCase::IgnoreCase );
if ( IsLOD && LodSplitId == -1 )
LodSplitId = SplitId;
// Materials maps (Houdini to Unreal) needs to be reset for each static mesh generated
// Only the first LOD resets those maps
if ( !IsLOD || ( IsLOD && LodIndex == 0 ) )
{
MapHoudiniMatIdToUnrealIndex.Empty();
MapHoudiniMatAttributesToUnrealIndex.Empty();
}
// Record split id in geo part.
// LODs must use the same SplitID since they belong to the same static mesh
HoudiniGeoPartObject.SplitId = !IsLOD ? SplitId : LodSplitId;
// Reset collision flags for the current GeoPartObj
HoudiniGeoPartObject.bIsRenderCollidable = false;
HoudiniGeoPartObject.bIsCollidable = false;
HoudiniGeoPartObject.bIsSimpleCollisionGeo = false;
HoudiniGeoPartObject.bIsUCXCollisionGeo = false;
// Reset the collision/socket added flags if needed
// For LODs, only reset the collision flag for the first LOD level
if ( !IsLOD || ( IsLOD && LodIndex == 0 ) )
{
HoudiniGeoPartObject.bHasCollisionBeenAdded = false;
HoudiniGeoPartObject.bHasSocketBeenAdded = false;
}
// Determining the type of collision:
// UCX and simple collisions need to be checked first as they both start in the same way
// as their non UCX/non simple equivalent!}
if ( SplitGroupName.StartsWith( UCXCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) )
{
HoudiniGeoPartObject.bIsUCXCollisionGeo = true;
}
else if ( SplitGroupName.StartsWith( UCXRenderedCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) )
{
HoudiniGeoPartObject.bIsRenderCollidable = true;
HoudiniGeoPartObject.bIsUCXCollisionGeo = true;
}
else if ( SplitGroupName.StartsWith( SimpleRenderedCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) )
{
HoudiniGeoPartObject.bIsRenderCollidable = true;
HoudiniGeoPartObject.bIsSimpleCollisionGeo = true;
}
else if ( SplitGroupName.StartsWith( SimpleCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) )
{
HoudiniGeoPartObject.bIsCollidable = true;
HoudiniGeoPartObject.bIsSimpleCollisionGeo = true;
}
else if ( SplitGroupName.StartsWith( RenderedCollisionGroupNamePrefix, ESearchCase::IgnoreCase ) )
{
HoudiniGeoPartObject.bIsRenderCollidable = true;
}
else if ( SplitGroupName.StartsWith( HoudiniRuntimeSettings->CollisionGroupNamePrefix, ESearchCase::IgnoreCase ) )
{
HoudiniGeoPartObject.bIsCollidable = true;
}
// Handling UCX/Convex Hull colliders
if ( HoudiniGeoPartObject.bIsUCXCollisionGeo )
{
// Retrieve the vertices positions if necessary
if ( PartPositions.Num() <= 0 )
{
if ( !FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId,
PartInfo.id, HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, PartPositions ) )
{
// Error retrieving positions.
HOUDINI_LOG_WARNING(
TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] unable to retrieve position data ")
TEXT("- skipping."),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName );
break;
}
}
// Use multiple convex hulls?
bool MultiHullDecomp = false;
if ( SplitGroupName.Contains( TEXT("ucx_multi"), ESearchCase::IgnoreCase ) )
MultiHullDecomp = true;
// Create the convex hull colliders and add them to the Aggregate
if ( AddConvexCollisionToAggregate( PartPositions, SplitGroupVertexList, MultiHullDecomp, AggregateCollisionGeo ) )
{
// We'll add the collision after all the meshes are generated unless this a rendered_collision_geo_ucx
bHasAggregateGeometryCollision = true;
}
// No need to create a mesh if the colliders is not visible
if ( !HoudiniGeoPartObject.bIsRenderCollidable )
continue;
}
// Record split group name.
HoudiniGeoPartObject.SplitName = SplitGroupName;
// Attempt to locate a static mesh from previous instantiation to reuse it
UStaticMesh * const * FoundStaticMesh = nullptr;
if (IsLOD && LodIndex > 0)
{
// LODs levels other than the first one need to reuse StaticMesh from the output!
// as we want the additional LODs to be added to the newly created Static Mesh
FoundStaticMesh = StaticMeshesOut.Find(HoudiniGeoPartObject);
if (!FoundStaticMesh)
{
// If we failed, try to find the mesh via names
for (TMap< FHoudiniGeoPartObject, UStaticMesh * >::TConstIterator tIt(StaticMeshesOut); tIt; ++tIt)
{
const FHoudiniGeoPartObject& key = tIt.Key();
if (key.CompareNames(HoudiniGeoPartObject))
{
FoundStaticMesh = &tIt.Value();
break;
}
}
}
}
else
{
// We're not an LOD, try to locate a corresponding SM in the previously cooked objects
FoundStaticMesh = StaticMeshesIn.Find(HoudiniGeoPartObject);
if (!FoundStaticMesh)
{
// If we failed, try to find the previous mesh via names to help reuse of components
// the GUID doesnt always match even though its the same part on same HDA
for (TMap< FHoudiniGeoPartObject, UStaticMesh * >::TConstIterator tIt(StaticMeshesIn); tIt; ++tIt)
{
const FHoudiniGeoPartObject& key = tIt.Key();
if (key.CompareNames(HoudiniGeoPartObject))
{
FoundStaticMesh = &tIt.Value();
break;
}
}
}
}
// Flag whether we need to rebuild the mesh.
bool bRebuildStaticMesh = false;
// If the geometry and scaling factor have changed or if the user asked for a cook manually,
// we will need to rebuild the static mesh. If not, then we can reuse the corresponding static mesh.
if ( GeoInfo.hasGeoChanged || ForceRebuildStaticMesh || ForceRecookAll )
bRebuildStaticMesh = true;
// The geometry has not changed,
if ( !bRebuildStaticMesh )
{
// No mesh located, unless this split is a simple/convex collider, this is an error.
if ( !FoundStaticMesh && ( !HoudiniGeoPartObject.bIsSimpleCollisionGeo && !HoudiniGeoPartObject.bIsUCXCollisionGeo ) )
{
HOUDINI_LOG_ERROR(
TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] geometry has not changed ")
TEXT("but static mesh does not exist - skipping."),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName);
continue;
}
// If any of the materials on corresponding geo part object have not changed.
if ( !bMaterialsChanged && FoundStaticMesh && *FoundStaticMesh )
{
// We can reuse previously created geometry.
StaticMeshesOut.Add( HoudiniGeoPartObject, *FoundStaticMesh );
continue;
}
}
// If the static mesh was not located, we need to create a new one.
bool bStaticMeshCreated = false;
UStaticMesh * StaticMesh = nullptr;
if ( !FoundStaticMesh || *FoundStaticMesh == nullptr )
{
FGuid MeshGuid;
MeshGuid.Invalidate();
FString MeshName;
UPackage * MeshPackage = FHoudiniEngineBakeUtils::BakeCreateStaticMeshPackageForComponent(
HoudiniCookParams, HoudiniGeoPartObject, MeshName, MeshGuid );
if( !MeshPackage || MeshPackage->IsPendingKill() )
continue;
StaticMesh = NewObject< UStaticMesh >(
MeshPackage, FName( *MeshName ),
( HoudiniCookParams.StaticMeshBakeMode == EBakeMode::Intermediate ) ? RF_NoFlags : RF_Public | RF_Standalone );
// Add meta information to this package.
FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage(
MeshPackage, MeshPackage, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT( "true" ) );
FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage(
MeshPackage, MeshPackage, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MeshName );
// Notify system that new asset has been created.
//FAssetRegistryModule::AssetCreated( StaticMesh );
bStaticMeshCreated = true;
}
else
{
// Found the corresponding Static Mesh, just reuse it.
StaticMesh = *FoundStaticMesh;
// Try to reuse the SM's LOD groupd instead of the default one
LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(StaticMesh->LODGroup);
}
// Free any RHI resources for existing mesh before we re-create in place.
StaticMesh->PreEditChange(NULL);
if ( !IsLOD || LodIndex == 0 )
{
// We need to initialize the LODs used by this mesh
int32 NeededLODs = IsLOD ? NumberOfLODs : 1;
while (StaticMesh->GetNumSourceModels() < NeededLODs)
StaticMesh->AddSourceModel();
// We may have to remove excessive LOD levels
if ( StaticMesh->GetNumSourceModels() > NeededLODs )
StaticMesh->SetNumSourceModels(NeededLODs);
}
// Grab the corresponding SourceModel
int32 SrcModelIdx = IsLOD ? LodIndex : 0;
FStaticMeshSourceModel* SrcModel = (StaticMesh->IsSourceModelValid(SrcModelIdx)) ? &(StaticMesh->GetSourceModel(SrcModelIdx)) : 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."),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName, IsLOD ? LodIndex : 0 );
continue;
}
// Load existing raw model. This will be empty as we are constructing a new mesh.
FRawMesh RawMesh;
int32 SplitGroupFaceCount = SplitGroupFaceIndices.Num();
if (!bRebuildStaticMesh)
{
// We dont need to rebuild the mesh (because the geometry hasn't changed, but the materials have)
// So we can just load the old data into the Raw mesh and reuse it.
SrcModel->LoadRawMesh(RawMesh);
}
else
{
//---------------------------------------------------------------------------------------------------------------------
// NORMALS AND TANGENTS
//---------------------------------------------------------------------------------------------------------------------
TArray< float > SplitGroupNormals;
// No need to read the normals if we'll recompute them after
bool bReadNormals = HoudiniRuntimeSettings->RecomputeNormalsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always;
if ( bReadNormals )
{
if ( PartNormals.Num() <= 0 )
{
// Retrieve normal data for this part
FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId,
PartInfo.id, HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, PartNormals );
}
// See if we need to transfer normal point attributes to vertex attributes.
FHoudiniEngineUtils::TransferRegularPointAttributesToVertices(
SplitGroupVertexList, AttribInfoNormals, PartNormals, SplitGroupNormals );
}
TArray< float > SplitGroupTangentU;
TArray< float > SplitGroupTangentV;
// No need to read the tangents if we always want to recompute them
bool bReadTangents = HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always;
if (bReadTangents)
{
if (PartTangentU.Num() <= 0)
{
// Retrieve TangentU data for this part
FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id,
HAPI_UNREAL_ATTRIB_TANGENTU, AttribInfoTangentU, PartTangentU );
}
// Transfer tangentu point attributes to the vertices if needed.
FHoudiniEngineUtils::TransferRegularPointAttributesToVertices(
SplitGroupVertexList, AttribInfoTangentU, PartTangentU, SplitGroupTangentU);
if (PartTangentV.Num() <= 0)
{
// Retrieve TangentV data for this part
FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id,
HAPI_UNREAL_ATTRIB_TANGENTV, AttribInfoTangentV, PartTangentV);
}
// Transfer tangentv point attributes to the vertices if needed.
FHoudiniEngineUtils::TransferRegularPointAttributesToVertices(
SplitGroupVertexList, AttribInfoTangentV, PartTangentV, SplitGroupTangentV);
}
// We need to generate tangents if we have normals but we dont have tangentu or tangentv attributes
bool bGenerateTangents = ( SplitGroupNormals.Num() > 0 ) && ( SplitGroupTangentU.Num() <= 0 || SplitGroupTangentV.Num() <= 0 );
if ( bGenerateTangents && ( HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always ) )
{
// No need to generate tangents if unreal will recompute them after
bGenerateTangents = false;
}
// Transfer normals.
int32 WedgeNormalCount = SplitGroupNormals.Num() / 3;
// Ensure the number of Normal values is correct
if ( SplitGroupNormals.Num() > 0
&& !SplitGroupNormals.IsValidIndex( (WedgeNormalCount - 1) * 3 + 2 ) )
{
// Ignore normals
WedgeNormalCount = 0;
HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals."));
}
// Transfer the normals and generate the tangents if needed
RawMesh.WedgeTangentZ.SetNumZeroed( WedgeNormalCount );
for ( int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx )
{
FVector WedgeTangentZ;
WedgeTangentZ.X = SplitGroupNormals[ WedgeTangentZIdx * 3 + 0 ];
if ( ImportAxis == HRSAI_Unreal )
{
// We need to flip Z and Y coordinate
WedgeTangentZ.Y = SplitGroupNormals[ WedgeTangentZIdx * 3 + 2 ];
WedgeTangentZ.Z = SplitGroupNormals[ WedgeTangentZIdx * 3 + 1 ];
}
else
{
WedgeTangentZ.Y = SplitGroupNormals[ WedgeTangentZIdx * 3 + 1 ];
WedgeTangentZ.Z = SplitGroupNormals[ WedgeTangentZIdx * 3 + 2 ];
}
RawMesh.WedgeTangentZ[ WedgeTangentZIdx ] = WedgeTangentZ;
// If we need to generate tangents.
if ( bGenerateTangents )
{
FVector TangentX, TangentY;
WedgeTangentZ.FindBestAxisVectors( TangentX, TangentY );
RawMesh.WedgeTangentX.Add( TangentX );
RawMesh.WedgeTangentY.Add( TangentY );
}
}
// Only add tangents if we have some and do not plan on generating them.
// We also need to make sure that the number of tangents matches the number of normals
bool bAddTangents = !bGenerateTangents && bReadTangents;
int32 WedgeTangentUCount = SplitGroupTangentU.Num() / 3;
int32 WedgeTangentVCount = SplitGroupTangentV.Num() / 3;
if ( WedgeTangentUCount != WedgeNormalCount || WedgeTangentVCount != WedgeNormalCount )
bAddTangents = false;
if ( bAddTangents )
{
// Transfer tangents if we have them and they're valid
RawMesh.WedgeTangentX.SetNumZeroed(WedgeTangentUCount);
for (int32 WedgeTangentUIdx = 0; WedgeTangentUIdx < WedgeTangentUCount; ++WedgeTangentUIdx)
{
FVector WedgeTangentX;
WedgeTangentX.X = SplitGroupTangentU[WedgeTangentUIdx * 3 + 0];
if (ImportAxis == HRSAI_Unreal)
{
// We need to flip Z and Y coordinate
WedgeTangentX.Y = SplitGroupTangentU[WedgeTangentUIdx * 3 + 2];
WedgeTangentX.Z = SplitGroupTangentU[WedgeTangentUIdx * 3 + 1];
}
else
{
WedgeTangentX.Y = SplitGroupTangentU[WedgeTangentUIdx * 3 + 1];
WedgeTangentX.Z = SplitGroupTangentU[WedgeTangentUIdx * 3 + 2];
}
RawMesh.WedgeTangentX[WedgeTangentUIdx] = WedgeTangentX;
}
RawMesh.WedgeTangentY.SetNumZeroed(WedgeTangentVCount);
for (int32 WedgeTangentVIdx = 0; WedgeTangentVIdx < WedgeTangentVCount; ++WedgeTangentVIdx)
{
FVector WedgeTangentY;
WedgeTangentY.X = SplitGroupTangentV[WedgeTangentVIdx * 3 + 0];
if (ImportAxis == HRSAI_Unreal)
{
// We need to flip Z and Y coordinate
WedgeTangentY.Y = SplitGroupTangentV[WedgeTangentVIdx * 3 + 2];
WedgeTangentY.Z = SplitGroupTangentV[WedgeTangentVIdx * 3 + 1];
}
else
{
WedgeTangentY.Y = SplitGroupTangentV[WedgeTangentVIdx * 3 + 1];
WedgeTangentY.Z = SplitGroupTangentV[WedgeTangentVIdx * 3 + 2];
}
RawMesh.WedgeTangentY[WedgeTangentVIdx] = WedgeTangentY;
}
}
//---------------------------------------------------------------------------------------------------------------------
// VERTEX COLORS AND ALPHAS
//---------------------------------------------------------------------------------------------------------------------
TArray< float > SplitGroupColors;
if ( PartColors.Num() <= 0 )
{
// Retrieve color data
FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId,
PartInfo.id, HAPI_UNREAL_ATTRIB_COLOR, AttribInfoColors, PartColors );
}
// See if we need to transfer color point attributes to vertex attributes.
FHoudiniEngineUtils::TransferRegularPointAttributesToVertices(
SplitGroupVertexList, AttribInfoColors, PartColors, SplitGroupColors );
TArray< float > SplitGroupAlphas;
if ( PartAlphas.Num() <= 0 )
{
// Retrieve alpha data
FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId,
PartInfo.id, HAPI_UNREAL_ATTRIB_ALPHA, AttribInfoAlpha, PartAlphas );
}
// See if we need to transfer alpha point attributes to vertex attributes.
FHoudiniEngineUtils::TransferRegularPointAttributesToVertices(
SplitGroupVertexList, AttribInfoAlpha, PartAlphas, SplitGroupAlphas );
// Transfer colors and alphas to the raw mesh
if ( AttribInfoColors.exists && ( AttribInfoColors.tupleSize > 0 ) )
{
int32 WedgeColorsCount = SplitGroupColors.Num() / AttribInfoColors.tupleSize;
// Ensure the number of color values is correct
if (!SplitGroupColors.IsValidIndex( (WedgeColorsCount - 1) * 3 + 2) )
{
// Ignore colors
WedgeColorsCount = 0;
HOUDINI_LOG_WARNING(TEXT("Invalid vertex color count detected - Skipping colors."));
}
RawMesh.WedgeColors.SetNumZeroed( WedgeColorsCount );
for ( int32 WedgeColorIdx = 0; WedgeColorIdx < WedgeColorsCount; ++WedgeColorIdx )
{
FLinearColor WedgeColor;
WedgeColor.R = FMath::Clamp(
SplitGroupColors[ WedgeColorIdx * AttribInfoColors.tupleSize + 0 ], 0.0f, 1.0f );
WedgeColor.G = FMath::Clamp(
SplitGroupColors[ WedgeColorIdx * AttribInfoColors.tupleSize + 1 ], 0.0f, 1.0f );
WedgeColor.B = FMath::Clamp(
SplitGroupColors[ WedgeColorIdx * AttribInfoColors.tupleSize + 2 ], 0.0f, 1.0f );
if( AttribInfoAlpha.exists )
{
WedgeColor.A = FMath::Clamp( SplitGroupAlphas[ WedgeColorIdx ], 0.0f, 1.0f );
}
else if ( AttribInfoColors.tupleSize == 4 )
{
// We have alpha.
WedgeColor.A = FMath::Clamp(
SplitGroupColors[ 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
{
// No Colors or Alphas, init colors to White
FColor DefaultWedgeColor = FLinearColor::White.ToFColor( false );
int32 WedgeColorsCount = RawMesh.WedgeIndices.Num();
if ( WedgeColorsCount > 0 )
RawMesh.WedgeColors.Init( DefaultWedgeColor, WedgeColorsCount );
}
//---------------------------------------------------------------------------------------------------------------------
// FACE SMOOTHING
//---------------------------------------------------------------------------------------------------------------------
// Retrieve face smoothing data.
if ( PartFaceSmoothingMasks.Num() <= 0 )
{
FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId,
PartInfo.id, MarshallingAttributeNameFaceSmoothingMask.c_str(),
AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks );
}
// Set face smoothing masks.
RawMesh.FaceSmoothingMasks.SetNumZeroed( SplitGroupFaceCount );
if ( PartFaceSmoothingMasks.Num() )
{
int32 ValidFaceIdx = 0;
for ( int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx += 3 )
{
int32 WedgeCheck = SplitGroupVertexList[ VertexIdx + 0 ];
if ( WedgeCheck == -1 )
continue;
RawMesh.FaceSmoothingMasks[ ValidFaceIdx ] = PartFaceSmoothingMasks[ VertexIdx / 3 ];
ValidFaceIdx++;
}
}
//---------------------------------------------------------------------------------------------------------------------
// UVS
//---------------------------------------------------------------------------------------------------------------------
// Extract all UV sets
TArray< TArray< float > > SplitGroupUVs;
SplitGroupUVs.SetNumZeroed( MAX_STATIC_TEXCOORDS );
if ( PartUVs.Num() && PartUVs[0].Num() <= 0 )
{
// Retrieve all the UVs sets for this part
FHoudiniEngineUtils::GetAllUVAttributesInfoAndTexCoords(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id,
AttribInfoUVs, PartUVs );
}
// See if we need to transfer uv point attributes to vertex attributes.
for ( int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx )
{
FHoudiniEngineUtils::TransferRegularPointAttributesToVertices(
SplitGroupVertexList, AttribInfoUVs[ TexCoordIdx ], PartUVs[ TexCoordIdx ], SplitGroupUVs[ TexCoordIdx ] );
}
// Transfer UVs to the Raw Mesh
int32 UVChannelCount = 0;
int32 LightMapUVChannel = 0;
for ( int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx )
{
TArray< float > & TextureCoordinate = SplitGroupUVs[ TexCoordIdx ];
int32 WedgeUVCount = TextureCoordinate.Num() / 2;
if ( TextureCoordinate.Num() > 0 && TextureCoordinate.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.
FVector2D WedgeUV;
WedgeUV.X = TextureCoordinate[ WedgeUVIdx * 2 + 0 ];
WedgeUV.Y = 1.0f - TextureCoordinate[ WedgeUVIdx * 2 + 1 ];
RawMesh.WedgeTexCoords[ TexCoordIdx ][ WedgeUVIdx ] = WedgeUV;
}
UVChannelCount++;
if ( UVChannelCount <= 2 )
LightMapUVChannel = TexCoordIdx;
}
else
{
RawMesh.WedgeTexCoords[ TexCoordIdx ].Empty();
}
}
// We have to have at least one UV channel. If there's none, create one with zero data.
if (UVChannelCount == 0)
RawMesh.WedgeTexCoords[ 0 ].SetNumZeroed( SplitGroupVertexListCount );
// Set the lightmap Coordinate Index
// If we have more than one UV set, the 2nd set will be used for lightmaps by convention
// If not, the first UV set will be used
StaticMesh->LightMapCoordinateIndex = LightMapUVChannel;
//---------------------------------------------------------------------------------------------------------------------
// LIGHTMAP RESOLUTION
//---------------------------------------------------------------------------------------------------------------------
// Get lightmap resolution (if present).
if ( PartLightMapResolutions.Num() <= 0 )
{
FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId,
PartInfo.id, MarshallingAttributeNameLightmapResolution.c_str(),
AttribLightmapResolution, PartLightMapResolutions );
}
// make sure the mesh has a new lighting guid
StaticMesh->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< int32 > IndicesMapper;
IndicesMapper.Init( -1, SplitGroupVertexList.Num() );
int32 CurrentMapperIndex = 0;
// Neededvertices:
// Contains the old index of the needed vertices for the current split
// NeededVertices[ newIndex ] => oldIndex
TArray< int32 > NeededVertices;
RawMesh.WedgeIndices.SetNumZeroed( SplitGroupVertexListCount );
int32 ValidVertexId = 0;
for ( int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx += 3 )
{
int32 WedgeCheck = SplitGroupVertexList[ VertexIdx + 0 ];
if ( WedgeCheck == -1 )
continue;
int32 WedgeIndices[ 3 ] =
{
SplitGroupVertexList[ VertexIdx + 0 ],
SplitGroupVertexList[ VertexIdx + 1 ],
SplitGroupVertexList[ 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 index "),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *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 was not yet "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;
if ( ImportAxis == HRSAI_Unreal )
{
// 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 ] );
}
else if ( ImportAxis == HRSAI_Houdini )
{
// Dont flip the wedge indices
RawMesh.WedgeIndices[ ValidVertexId + 0 ] = WedgeIndices[ 0 ];
RawMesh.WedgeIndices[ ValidVertexId + 1 ] = WedgeIndices[ 1 ];
RawMesh.WedgeIndices[ ValidVertexId + 2 ] = WedgeIndices[ 2 ];
}
ValidVertexId += 3;
}
//---------------------------------------------------------------------------------------------------------------------
// POSITIONS
//---------------------------------------------------------------------------------------------------------------------
// We may already have gotten the positions when creating the ucx collisions
if ( PartPositions.Num() <= 0 )
{
// Retrieve position data.
if ( !FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId,
PartInfo.id, HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, PartPositions ) )
{
// Error retrieving positions.
HOUDINI_LOG_WARNING(
TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unable to retrieve position data ")
TEXT("- skipping."),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName );
if ( bStaticMeshCreated )
StaticMesh->MarkPendingKill();
break;
}
}
//
// 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."),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName);
}
FVector VertexPosition;
VertexPosition.X = PartPositions[ NeededVertexIndex * 3 + 0 ] * GeneratedGeometryScaleFactor;
if ( ImportAxis == HRSAI_Unreal )
{
// We need to swap Z and Y coordinate here.
VertexPosition.Y = PartPositions[ NeededVertexIndex * 3 + 2 ] * GeneratedGeometryScaleFactor;
VertexPosition.Z = PartPositions[ NeededVertexIndex * 3 + 1 ] * GeneratedGeometryScaleFactor;
}
else if ( ImportAxis == HRSAI_Houdini )
{
// No swap required.
VertexPosition.Y = PartPositions[ NeededVertexIndex * 3 + 1 ] * GeneratedGeometryScaleFactor;
VertexPosition.Z = PartPositions[ NeededVertexIndex * 3 + 2 ] * GeneratedGeometryScaleFactor;
}
RawMesh.VertexPositions[ VertexPositionIdx ] = VertexPosition;
}
// We need to 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
//---------------------------------------------------------------------------------------------------------------------
// See if we have material override attributes
if ( PartFaceMaterialAttributeOverrides.Num() <= 0 )
{
FHoudiniEngineUtils::HapiGetAttributeDataAsString(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id,
MarshallingAttributeNameMaterial.c_str(),
AttribFaceMaterials, PartFaceMaterialAttributeOverrides );
// If material attribute was not found, check fallback compatibility attribute.
if ( !AttribFaceMaterials.exists )
{
PartFaceMaterialAttributeOverrides.Empty();
FHoudiniEngineUtils::HapiGetAttributeDataAsString(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id,
MarshallingAttributeNameMaterialFallback.c_str(),
AttribFaceMaterials, PartFaceMaterialAttributeOverrides );
}
// If material attribute and fallbacks were not found, check the material instance attribute.
if ( !AttribFaceMaterials.exists )
{
PartFaceMaterialAttributeOverrides.Empty();
FHoudiniEngineUtils::HapiGetAttributeDataAsString(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id,
MarshallingAttributeNameMaterialInstance.c_str(),
AttribFaceMaterials, PartFaceMaterialAttributeOverrides);
}
if ( AttribFaceMaterials.exists && AttribFaceMaterials.owner != HAPI_ATTROWNER_PRIM && AttribFaceMaterials.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." ),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName);
AttribFaceMaterials.exists = false;
PartFaceMaterialAttributeOverrides.Empty();
}
// If the material name was assigned per detail we replicate it for each primitive.
if ( PartFaceMaterialAttributeOverrides.Num() > 0 && AttribFaceMaterials.owner == HAPI_ATTROWNER_DETAIL )
{
FString SingleFaceMaterial = PartFaceMaterialAttributeOverrides[ 0 ];
PartFaceMaterialAttributeOverrides.Init( SingleFaceMaterial, SplitGroupVertexList.Num() / 3 );
}
}
//---------------------------------------------------------------------------------------------------------------------
// FACE MATERIALS
//---------------------------------------------------------------------------------------------------------------------
// Process material overrides first.
if ( PartFaceMaterialAttributeOverrides.Num() > 0 )
{
// Clear the previously generated materials ( unless we're not the first lod level )
if ( !IsLOD || ( IsLOD && LodIndex == 0 ) )
StaticMesh->StaticMaterials.Empty();
RawMesh.FaceMaterialIndices.SetNumZeroed( SplitGroupFaceCount );
for ( int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx )
{
int32 SplitFaceIndex = SplitGroupFaceIndices[ FaceIdx ];
if ( !PartFaceMaterialAttributeOverrides.IsValidIndex( SplitFaceIndex ) )
continue;
const FString & MaterialName = PartFaceMaterialAttributeOverrides[ SplitFaceIndex ];
int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find( MaterialName );
int32 CurrentFaceMaterialIdx = 0;
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 )
{
// Make sure this material is in the assignments before replacing it.
if( !HoudiniCookParams.HoudiniCookManager->GetAssignmentMaterial( MaterialInterface->GetName() ) )
HoudiniCookParams.HoudiniCookManager->AddAssignmentMaterial( MaterialInterface->GetName(), MaterialInterface );
// See if we have a replacement material for this.
UMaterialInterface * ReplacementMaterialInterface = HoudiniCookParams.HoudiniCookManager->GetReplacementMaterial( HoudiniGeoPartObject, MaterialInterface->GetName() );
if( ReplacementMaterialInterface )
MaterialInterface = ReplacementMaterialInterface;
// Add this material to the map
CurrentFaceMaterialIdx = StaticMesh->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<UMaterialInterface>(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get());
// We need to add this material to the map
FString MaterialShopName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME;
FHoudiniEngineMaterialUtils::GetUniqueMaterialShopName( AssetId, MaterialId, MaterialShopName );
UMaterialInterface * const * FoundMaterial = Materials.Find( MaterialShopName );
if ( FoundMaterial )
MaterialInterface = *FoundMaterial;
// If we have a replacement material for this geo part object and this shop material name.
UMaterialInterface * ReplacementMaterial =
HoudiniCookParams.HoudiniCookManager->GetReplacementMaterial( HoudiniGeoPartObject, MaterialShopName );
if ( ReplacementMaterial )
MaterialInterface = ReplacementMaterial;
// Add the material to the Static mesh
CurrentFaceMaterialIdx = StaticMesh->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 ( bPartHasMaterials )
{
if ( bSingleFaceMaterial )
{
// Use default Houdini material if no valid material is assigned to any of the faces.
UMaterialInterface * Material = Cast<UMaterialInterface>(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get());
// We have only one material.
RawMesh.FaceMaterialIndices.SetNumZeroed( SplitGroupFaceCount );
// Get id of this single material.
FString MaterialShopName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME;
FHoudiniEngineMaterialUtils::GetUniqueMaterialShopName( AssetId, PartFaceMaterialIds[ 0 ], MaterialShopName );
UMaterialInterface * const * FoundMaterial = Materials.Find( MaterialShopName );
if ( FoundMaterial )
Material = *FoundMaterial;
// If we have replacement material for this geo part object and this shop material name.
UMaterialInterface * ReplacementMaterial =
HoudiniCookParams.HoudiniCookManager->GetReplacementMaterial( HoudiniGeoPartObject, MaterialShopName );
if ( ReplacementMaterial )
Material = ReplacementMaterial;
StaticMesh->StaticMaterials.Empty();
StaticMesh->StaticMaterials.Add( FStaticMaterial(Material) );
}
else
{
// We have multiple materials
// Clear the previously generated materials ( unless we're not the first lod level )
if ( !IsLOD || ( IsLOD && LodIndex == 0 ) )
StaticMesh->StaticMaterials.Empty();
// Get default Houdini material.
UMaterial * MaterialDefault = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get();
// Reset Rawmesh material face assignments.
RawMesh.FaceMaterialIndices.SetNumZeroed( SplitGroupFaceCount );
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 assign the mat index
RawMesh.FaceMaterialIndices[ FaceIdx ] = *FoundUnrealMatIndex;
continue;
}
UMaterialInterface * Material = Cast<UMaterialInterface>(MaterialDefault);
FString MaterialShopName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME;
FHoudiniEngineMaterialUtils::GetUniqueMaterialShopName( AssetId, MaterialId, MaterialShopName );
UMaterialInterface * const * FoundMaterial = Materials.Find( MaterialShopName );
if ( FoundMaterial )
Material = *FoundMaterial;
// See if we have replacement material for this geo part object and this shop material name.
UMaterialInterface * ReplacementMaterial =
HoudiniCookParams.HoudiniCookManager->GetReplacementMaterial( HoudiniGeoPartObject, MaterialShopName );
if ( ReplacementMaterial )
Material = ReplacementMaterial;
// Add the material to the Static mesh
int32 UnrealMatIndex = StaticMesh->StaticMaterials.Add( FStaticMaterial( Material ) );
// 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.
RawMesh.FaceMaterialIndices.SetNumZeroed( SplitGroupFaceCount );
UMaterialInterface * Material = Cast<UMaterialInterface>(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get());
FString MaterialShopName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME;
// If we have replacement material for this geo part object and this shop material name.
UMaterialInterface * ReplacementMaterial =
HoudiniCookParams.HoudiniCookManager->GetReplacementMaterial( HoudiniGeoPartObject, MaterialShopName );
if ( ReplacementMaterial )
Material = ReplacementMaterial;
StaticMesh->StaticMaterials.Empty();
StaticMesh->StaticMaterials.Add( FStaticMaterial(Material) );
}
}
// Some mesh generation settings.
HoudiniRuntimeSettings->SetMeshBuildSettings( SrcModel->BuildSettings, RawMesh );
// By default the distance field resolution should be set to 2.0
SrcModel->BuildSettings.DistanceFieldResolutionScale = HoudiniCookParams.GeneratedDistanceFieldResolutionScale;
// We need to check light map uv set for correctness. Unreal seems to have occasional issues with
// zero UV sets when building lightmaps.
int32 LightMapResolutionOverride = -1;
if ( SrcModel->BuildSettings.bGenerateLightmapUVs )
{
// See if we need to disable lightmap generation because of bad UVs.
if ( FHoudiniEngineUtils::ContainsInvalidLightmapFaces( RawMesh, StaticMesh->LightMapCoordinateIndex ) )
{
SrcModel->BuildSettings.bGenerateLightmapUVs = false;
HOUDINI_LOG_MESSAGE(
TEXT( "Skipping Lightmap Generation: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] invalid face detected " )
TEXT( "- skipping." ),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName );
}
if( PartLightMapResolutions.Num() > 0 )
LightMapResolutionOverride = PartLightMapResolutions[ 0 ];
// Apply lightmap resolution override if it has been specified
if ( LightMapResolutionOverride > 0 )
StaticMesh->LightMapResolution = LightMapResolutionOverride;
}
if ( !RawMesh.IsValidOrFixable() )
{
HOUDINI_LOG_WARNING(
TEXT("Static Mesh Generated from Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] is invalid!")
TEXT("- skipping."),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName);
if ( bStaticMeshCreated )
StaticMesh->MarkPendingKill();
continue;
}
// This is required due to the impeding deprecation of FRawMesh
// If we dont update this UE4 will crash upon deleting an asset.
SrcModel->StaticMeshOwner = StaticMesh;
// Store the new raw mesh.
SrcModel->SaveRawMesh(RawMesh);
// Lambda for initializing a LOD level
auto InitLODLevel = [ & ]( const int32& LODLevelIndex )
{
// Ensure that this LOD level exisits
while (StaticMesh->GetNumSourceModels() < (LODLevelIndex + 1))
StaticMesh->AddSourceModel();
// Set its reduction settings to the default
StaticMesh->GetSourceModel(LODLevelIndex).ReductionSettings = LODGroup.GetDefaultSettings( LODLevelIndex );
for ( int32 MaterialIndex = 0; MaterialIndex < StaticMesh->StaticMaterials.Num(); ++MaterialIndex )
{
FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get( LODLevelIndex, MaterialIndex );
Info.MaterialIndex = MaterialIndex;
Info.bEnableCollision = true;
Info.bCastShadow = true;
StaticMesh->GetSectionInfoMap().Set( LODLevelIndex, MaterialIndex, Info );
}
};
if ( !IsLOD )
{
// For non LODed mesh, init the default number of LODs
for ( int32 ModelLODIndex = 0; ModelLODIndex < DefaultNumLODs; ++ModelLODIndex )
InitLODLevel( ModelLODIndex );
}
else
{
// Init the current LOD level
InitLODLevel( LodIndex );
bool InvalidateLODAttr = false;
// If the "lod_screensize" attribute was not found, fallback to the "lodX_screensize" attribute
if ( !AttribInfoLODScreenSize.exists )
{
LODScreenSizes.Empty();
FString LODAttributeName = SplitGroupName + TEXT("_screensize");
FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id,
TCHAR_TO_ANSI(*LODAttributeName), AttribInfoLODScreenSize, LODScreenSizes);
InvalidateLODAttr = AttribInfoLODScreenSize.exists;
}
// finally, look for a potential uproperty style attribute
if ( !AttribInfoLODScreenSize.exists )
{
LODScreenSizes.Empty();
FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id,
"unreal_uproperty_screensize", AttribInfoLODScreenSize, LODScreenSizes);
InvalidateLODAttr = AttribInfoLODScreenSize.exists;
}
if ( AttribInfoLODScreenSize.exists )
{
float screensize = -1.0;
if ( AttribInfoLODScreenSize.owner == HAPI_ATTROWNER_PRIM )
{
int32 n = 0;
for( ; n < SplitGroupVertexList.Num(); n++ )
{
if ( SplitGroupVertexList[ n ] > 0 )
break;
}
screensize = LODScreenSizes[ n / 3 ];
}
else
{
// Handle single screensize attributes
if ( LODScreenSizes.Num() == 1 )
screensize = LODScreenSizes[ 0 ];
else
{
// Handle tuple screensize attributes
if ( LODScreenSizes.IsValidIndex( LodIndex ) )
screensize = LODScreenSizes[ LodIndex ];
else
screensize = 0.0f;
}
}
// Make sure the LOD Screensize is a percent, so if its above 1, divide by 100
if ( screensize > 1.0f )
screensize /= 100.0f;
// Only apply the LOD screensize if it's valid
if ( screensize >= 0.0f )
{
StaticMesh->GetSourceModel(LodIndex).ScreenSize = screensize;
StaticMesh->bAutoComputeLODScreenSize = false;
}
if ( InvalidateLODAttr )
AttribInfoLODScreenSize.exists = false;
}
// Increment the LODIndex
LodIndex++;
}
// The following actions needs to be done only once per Static Mesh,
// So if we are a LOD level other than the last one, skip this!
if ( IsLOD && ( LodIndex != NumberOfLODs ) )
{
// The First LOD still needs to add the mesh to the out list so we can reuse it for the next LOD levels
if ( LodIndex == 1 )
StaticMeshesOut.Add( HoudiniGeoPartObject, StaticMesh );
continue;
}
// Assign generation parameters for this static mesh.
HoudiniCookParams.HoudiniCookManager->SetStaticMeshGenerationParameters( StaticMesh );
// Make sure we remove the old simple colliders if needed
if( UBodySetup * BodySetup = StaticMesh->BodySetup )
{
// Simple colliders are from a previous cook, remove them!
if( !HoudiniGeoPartObject.bHasCollisionBeenAdded )
BodySetup->RemoveSimpleCollision();
// See if we need to enable collisions on the whole mesh.
if( ( HoudiniGeoPartObject.IsCollidable() || HoudiniGeoPartObject.IsRenderCollidable() )
&& ( !HoudiniGeoPartObject.bIsSimpleCollisionGeo && !HoudiniGeoPartObject.bIsUCXCollisionGeo ) )
{
// Enable collisions for this static mesh.
BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple;
}
else if ( !HoudiniGeoPartObject.bIsSimpleCollisionGeo && !HoudiniGeoPartObject.bIsUCXCollisionGeo )
{
// We dont have collider meshes, or simple colliders, if the LODForCollision uproperty attribute is set
// we need to activate complex collision for that lod to be picked up as collider
if ( HapiCheckAttributeExists( HoudiniGeoPartObject, "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL ) )
BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple;
}
}
// See if a custom bake name override for this mesh was assigned via the "unreal_bake_name" attribute
TArray< FString > BakeNameOverrides;
{
HAPI_AttributeInfo AttribBakeNameOverride;
FHoudiniApi::AttributeInfo_Init(&AttribBakeNameOverride);
FHoudiniEngineUtils::HapiGetAttributeDataAsString(
AssetId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id,
HAPI_UNREAL_ATTRIB_BAKE_NAME, AttribBakeNameOverride, BakeNameOverrides);
if (BakeNameOverrides.Num() > 0)
{
int32 AttrIdx = 0;
if (AttribBakeNameOverride.owner == HAPI_ATTROWNER_PRIM)
{
// If the attribute is on primitives, we need to find
// the index of the prim that correspond to our split
for (int32 n = 0; n < SplitGroupVertexList.Num(); n++)
{
if (SplitGroupVertexList[n] <= 0)
continue;
AttrIdx = n / 3;
break;
}
if (!BakeNameOverrides.IsValidIndex(AttrIdx))
AttrIdx = 0;
}
FString BakeNameOverride = BakeNameOverrides[AttrIdx];
if (!BakeNameOverride.IsEmpty())
{
// If the name override was set on the details and we have multiple split
// append the split name to the override to avoid collisions on bake
if (AttribBakeNameOverride.owner == HAPI_ATTROWNER_DETAIL
&& SplitGroupNames.Num() > 1)
{
if (!SplitGroupName.Equals("main_geo", ESearchCase::IgnoreCase))
BakeNameOverride += "_" + SplitGroupName;
}
FString& OverrideStrRef = HoudiniCookParams.BakeNameOverrides->FindOrAdd(HoudiniGeoPartObject);
OverrideStrRef = BakeNameOverride;
}
}
}
// Try to update the uproperties of the StaticMesh
UpdateUPropertyAttributesOnObject( StaticMesh, HoudiniGeoPartObject);
// BUILD the Static Mesh
FHoudiniScopedGlobalSilence ScopedGlobalSilence;
TArray< FText > BuildErrors;
{
SCOPE_CYCLE_COUNTER( STAT_BuildStaticMesh );
StaticMesh->Build( true, &BuildErrors );
}
for ( int32 BuildErrorIdx = 0; BuildErrorIdx < BuildErrors.Num(); ++BuildErrorIdx )
{
const FText & TextError = BuildErrors[ BuildErrorIdx ];
HOUDINI_LOG_MESSAGE(
TEXT( "Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] build error " )
TEXT( "- %s." ),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName, *( TextError.ToString() ) );
}
// Skip Invalid static meshes
if ( !StaticMesh || StaticMesh->IsPendingKill() )
{
HOUDINI_LOG_MESSAGE(
TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid Static Mesh created! ")
TEXT("- skipping."),
ObjectInfo.nodeId, *ObjectName, GeoInfo.nodeId, PartIdx, *PartName, SplitId, *SplitGroupName );
continue;
}
StaticMesh->GetOnMeshChanged().Broadcast();
// Do we need to add simple collisions ?
bool bSimpleCollisionAddedToAggregate = false;
if ( HoudiniGeoPartObject.bIsSimpleCollisionGeo )
{
if ( AddSimpleCollision( SplitGroupName, StaticMesh, HoudiniGeoPartObject, AggregateCollisionGeo, bSimpleCollisionAddedToAggregate ) )
{
if ( bSimpleCollisionAddedToAggregate )
{
// The colliders are invisible and have been added to the aggregate,
// we can skip to the next object and this collider will be added after
bHasAggregateGeometryCollision = true;
continue;
}
else
{
// We don't want these collisions to be removed afterwards
HoudiniGeoPartObject.bHasCollisionBeenAdded = true;
}
}
}
// If any simple collider was added to the aggregate, and this mesh is visible, add the colliders now
if ( bHasAggregateGeometryCollision )
{
// Add the aggregate collision geo to the static mesh
if ( AddAggregateCollisionGeometryToStaticMesh( StaticMesh, HoudiniGeoPartObject, AggregateCollisionGeo ) )
bHasAggregateGeometryCollision = false;
}
// Sockets are attached to the mesh after, for now, clear the existing one ( as they are from a previous cook )
if ( !HoudiniGeoPartObject.bHasSocketBeenAdded )
StaticMesh->Sockets.Empty();
// Notify that we created a new Static Mesh
if ( bStaticMeshCreated )
FAssetRegistryModule::AssetCreated( StaticMesh );
// Try to find the outer package so we can dirty it up
if (StaticMesh->GetOuter())
{
StaticMesh->GetOuter()->MarkPackageDirty();
}
else
{
StaticMesh->MarkPackageDirty();
}
StaticMeshesOut.Add( HoudiniGeoPartObject, StaticMesh );
} // end for SplitId
// Add the sockets we found to that part's meshes
if ( AllSockets.Num() > 0 )
{
bool SocketsAdded = false;
for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshesOut ); Iter; ++Iter )
{
FHoudiniGeoPartObject * CurrentHoudiniGeoPartObject = &(Iter.Key());
if ( (CurrentHoudiniGeoPartObject->ObjectId != ObjectInfo.nodeId )
|| (CurrentHoudiniGeoPartObject->GeoId != GeoInfo.nodeId )
|| (CurrentHoudiniGeoPartObject->PartId != PartIdx ) )
continue;
// This GeoPartObject is from the same object/geo, so we can add the sockets to it
if ( AddMeshSocketsToStaticMesh( Iter.Value(), *CurrentHoudiniGeoPartObject, AllSockets, AllSocketsNames, AllSocketsActors, AllSocketsTags ) )
SocketsAdded = true;
}
if ( SocketsAdded )
{
// Clean up the sockets for this part
AllSockets.Empty();
AllSocketsNames.Empty();
AllSocketsActors.Empty();
AllSocketsTags.Empty();
}
}
} // end for PartId
// There should be no UCX/Simple colliders left now
if ( bHasAggregateGeometryCollision )
HOUDINI_LOG_ERROR( TEXT("All Simple Colliders found in the HDA were not attached to a static mesh!!") );
// Add the sockets we found in the parts of this geo to the meshes it has generated
if ( AllSockets.Num() > 0 )
{
for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshesOut ); Iter; ++Iter )
{
FHoudiniGeoPartObject * HoudiniGeoPartObject = &( Iter.Key() );
if ( ( HoudiniGeoPartObject->ObjectId != ObjectInfo.nodeId ) || ( HoudiniGeoPartObject->GeoId != GeoInfo.nodeId ) )
continue;
// This GeoPartObject is from the same object/geo, so we can add the sockets to it
AddMeshSocketsToStaticMesh( Iter.Value(), *HoudiniGeoPartObject, AllSockets, AllSocketsNames, AllSocketsActors, AllSocketsTags );
}
}
// Clean up the sockets for this geo
AllSockets.Empty();
AllSocketsNames.Empty();
AllSocketsActors.Empty();
AllSocketsTags.Empty();
} // end for ObjectId
// 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 ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshesOut ); Iter; ++Iter )
{
FHoudiniGeoPartObject HoudiniGeoPartObject = Iter.Key();
// Only update for collidable objects
if ( HoudiniGeoPartObject.IsCollidable() || HoudiniGeoPartObject.IsRenderCollidable() )
{
UStaticMesh* StaticMesh = Iter.Value();
if ( !StaticMesh || StaticMesh->IsPendingKill() )
continue;
UBodySetup * BodySetup = StaticMesh->BodySetup;
if ( BodySetup && !BodySetup->IsPendingKill() )
{
// 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
if ( StaticMesh->NavCollision )
{
BodySetup->InvalidatePhysicsData();
BodySetup->CreatePhysicsMeshes();
//StaticMesh->NavCollision->InvalidatePhysicsData();
//StaticMesh->NavCollision->InvalidateCollision();
//StaticMesh->NavCollision->CookedFormatData.FlushData();
//StaticMesh->NavCollision->GatherCollision();
StaticMesh->NavCollision->Setup(BodySetup);
}
}
}
}
#endif
return true;
}
#if WITH_EDITOR
bool
FHoudiniEngineUtils::ContainsDegenerateTriangles( const FRawMesh & RawMesh )
{
int32 WedgeCount = RawMesh.WedgeIndices.Num();
for ( int32 WedgeIdx = 0; WedgeIdx < WedgeCount; WedgeIdx += 3 )
{
const FVector & Vertex0 = RawMesh.VertexPositions[ RawMesh.WedgeIndices[ WedgeIdx + 0 ] ];
const FVector & Vertex1 = RawMesh.VertexPositions[ RawMesh.WedgeIndices[ WedgeIdx + 1 ] ];
const FVector & Vertex2 = RawMesh.VertexPositions[ RawMesh.WedgeIndices[ WedgeIdx + 2 ] ];
// Strict equality will not detect properly all the degenerated triangles, we need to use Equals here
if ( Vertex0.Equals(Vertex1, THRESH_POINTS_ARE_SAME) || Vertex0.Equals(Vertex2, THRESH_POINTS_ARE_SAME) || Vertex1.Equals(Vertex2, THRESH_POINTS_ARE_SAME) )
return true;
}
return false;
}
int32
FHoudiniEngineUtils::CountDegenerateTriangles( const FRawMesh & RawMesh )
{
int32 DegenerateTriangleCount = 0;
int32 WedgeCount = RawMesh.WedgeIndices.Num();
for ( int32 WedgeIdx = 0; WedgeIdx < WedgeCount; WedgeIdx += 3 )
{
const FVector & Vertex0 = RawMesh.VertexPositions[ RawMesh.WedgeIndices[ WedgeIdx + 0 ] ];
const FVector & Vertex1 = RawMesh.VertexPositions[ RawMesh.WedgeIndices[ WedgeIdx + 1 ] ];
const FVector & Vertex2 = RawMesh.VertexPositions[ RawMesh.WedgeIndices[ WedgeIdx + 2 ] ];
// Strict equality will not detect properly all the degenerated triangles, we need to use Equals here
if( Vertex0.Equals(Vertex1, THRESH_POINTS_ARE_SAME) || Vertex0.Equals(Vertex2, THRESH_POINTS_ARE_SAME) || Vertex1.Equals(Vertex2, THRESH_POINTS_ARE_SAME) )
DegenerateTriangleCount++;
}
return DegenerateTriangleCount;
}
#endif
int32
FHoudiniEngineUtils::TransferRegularPointAttributesToVertices(
const TArray< int32 > & VertexList, const HAPI_AttributeInfo & AttribInfo, TArray< float > & Data)
{
TArray< float > VertexData;
int32 ValidWedgeCount = TransferRegularPointAttributesToVertices( VertexList, AttribInfo, Data, VertexData );
if ( ValidWedgeCount > 0 )
Data = VertexData;
return ValidWedgeCount;
}
int32
FHoudiniEngineUtils::TransferRegularPointAttributesToVertices(
const TArray<int32>& InVertexList,
const HAPI_AttributeInfo& InAttribInfo,
const TArray<float>& InData,
TArray<float>& OutVertexData)
{
if (!InAttribInfo.exists || InAttribInfo.tupleSize <= 0)
return 0;
int32 ValidWedgeCount = 0;
// Future optimization - see if we can do direct vertex transfer.
int32 WedgeCount = InVertexList.Num();
OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize);
int32 LastValidWedgeIdx = 0;
if (InAttribInfo.owner == HAPI_ATTROWNER_POINT)
{
// Point attribute transfer
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_PRIM)
{
// Primitive attribute transfer
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
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 if (InAttribInfo.owner == HAPI_ATTROWNER_VERTEX)
{
// Vertex attribute transfer
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
{
// Invalid attribute owner, shouldn't happen
check(false);
}
OutVertexData.SetNumZeroed(ValidWedgeCount * InAttribInfo.tupleSize);
return ValidWedgeCount;
}
char *
FHoudiniEngineUtils::ExtractRawName( const FString & Name )
{
if ( !Name.IsEmpty() )
{
std::string ConvertedString = TCHAR_TO_UTF8( *Name );
// Allocate space for unique string.
int32 UniqueNameBytes = ConvertedString.size() + 1;
char * UniqueName = static_cast< char * >( FMemory::Malloc( UniqueNameBytes ) );
FMemory::Memzero( UniqueName, UniqueNameBytes );
FMemory::Memcpy( UniqueName, ConvertedString.c_str(), ConvertedString.size() );
return UniqueName;
}
return nullptr;
}
#if WITH_EDITOR
void
FHoudiniEngineUtils::CreateFaceMaterialArray(
const TArray< UMaterialInterface * >& Materials, const TArray< int32 > & FaceMaterialIndices, TArray< char * > & OutStaticMeshFaceMaterials )
{
// We need to create list of unique materials.
TArray< char * > UniqueMaterialList;
UMaterialInterface * MaterialInterface;
char * UniqueName = nullptr;
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.
MaterialInterface = Cast<UMaterialInterface>(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get());
}
FString FullMaterialName = MaterialInterface->GetPathName();
UniqueName = FHoudiniEngineUtils::ExtractRawName( FullMaterialName );
UniqueMaterialList.Add( UniqueName );
}
}
else
{
// We do not have any materials, add default.
MaterialInterface = Cast<UMaterialInterface>(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get());
FString FullMaterialName = MaterialInterface->GetPathName();
UniqueName = FHoudiniEngineUtils::ExtractRawName( FullMaterialName );
UniqueMaterialList.Add( UniqueName );
}
for ( int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx )
{
int32 FaceMaterialIdx = FaceMaterialIndices[ FaceIdx ];
check( UniqueMaterialList.IsValidIndex(FaceMaterialIdx) );
OutStaticMeshFaceMaterials.Add( UniqueMaterialList[ FaceMaterialIdx ] );
}
}
void
FHoudiniEngineUtils::DeleteFaceMaterialArray( TArray< char * > & OutStaticMeshFaceMaterials )
{
TSet< char * > UniqueMaterials( OutStaticMeshFaceMaterials );
for ( TSet< char * >::TIterator Iter = UniqueMaterials.CreateIterator(); Iter; ++Iter )
{
char* MaterialName = *Iter;
FMemory::Free( MaterialName );
}
OutStaticMeshFaceMaterials.Empty();
}
#endif // WITH_EDITOR
void
FHoudiniEngineUtils::ExtractStringPositions( const FString & Positions, TArray< FVector > & OutPositions )
{
TArray< FString > PointStrings;
static const TCHAR * PositionSeparators[] =
{
TEXT( " " ),
TEXT( "," ),
};
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION;
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
if ( HoudiniRuntimeSettings )
{
GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor;
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
}
int32 NumCoords = Positions.ParseIntoArray( PointStrings, PositionSeparators, 2 );
for ( int32 CoordIdx = 0; CoordIdx < NumCoords; CoordIdx += 3 )
{
FVector Position;
Position.X = FCString::Atof( *PointStrings[ CoordIdx + 0 ] );
Position.Y = FCString::Atof( *PointStrings[ CoordIdx + 1 ] );
Position.Z = FCString::Atof( *PointStrings[ CoordIdx + 2 ] );
Position *= GeneratedGeometryScaleFactor;
if ( ImportAxis == HRSAI_Unreal )
{
Swap( Position.Y, Position.Z );
}
else if ( ImportAxis == HRSAI_Houdini )
{
// No action required.
}
else
{
// Not valid enum value.
check( 0 );
}
OutPositions.Add( Position );
}
}
void
FHoudiniEngineUtils::CreatePositionsString( const TArray< FVector > & Positions, FString & PositionString )
{
PositionString = TEXT( "" );
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION;
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
if ( HoudiniRuntimeSettings )
{
GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor;
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
}
for ( int32 Idx = 0; Idx < Positions.Num(); ++Idx )
{
FVector Position = Positions[ Idx ];
if ( ImportAxis == HRSAI_Unreal )
{
Swap( Position.Z, Position.Y );
}
else if ( ImportAxis == HRSAI_Houdini )
{
// No action required.
}
else
{
// Not valid enum value.
check( 0 );
}
if ( GeneratedGeometryScaleFactor != 0.0f )
Position /= GeneratedGeometryScaleFactor;
PositionString += FString::Printf( TEXT( "%f, %f, %f " ), Position.X, Position.Y, Position.Z );
}
}
void
FHoudiniEngineUtils::ConvertScaleAndFlipVectorData( const TArray< float > & DataRaw, TArray< FVector > & DataOut )
{
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION;
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
if ( HoudiniRuntimeSettings )
{
GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor;
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
}
for ( int32 Idx = 0; Idx < DataRaw.Num(); Idx += 3 )
{
FVector Point( DataRaw[ Idx + 0 ], DataRaw[ Idx + 1 ], DataRaw[ Idx + 2 ] );
Point *= GeneratedGeometryScaleFactor;
if ( ImportAxis == HRSAI_Unreal )
{
Swap(Point.Z, Point.Y);
}
else if ( ImportAxis == HRSAI_Houdini )
{
// No action required.
}
else
{
// Not valid enum value.
check( 0 );
}
DataOut.Add( Point );
}
}
FString
FHoudiniEngineUtils::HoudiniGetLibHAPIName()
{
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;
}
#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
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 = FHoudiniEngineUtils::HoudiniGetLibHAPIName();
// 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"));
// Fallback in case we're using the steam version
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;
}
int32
FHoudiniEngineUtils::HapiGetVertexListForGroup(
HAPI_NodeId AssetId, HAPI_NodeId ObjectId, HAPI_NodeId GeoId,
HAPI_PartId PartId, const FString & GroupName,
const TArray< int32 > & FullVertexList, TArray< int32 > & NewVertexList,
TArray< int32 > & AllVertexList, TArray< int32 > & AllFaceList,
TArray< int32 > & AllCollisionFaceIndices, const bool& isPackedPrim )
{
NewVertexList.Init( -1, FullVertexList.Num() );
int32 ProcessedWedges = 0;
AllFaceList.Empty();
TArray< int32 > PartGroupMembership;
FHoudiniEngineUtils::HapiGetGroupMembership(
AssetId, ObjectId, GeoId, PartId, HAPI_GROUPTYPE_PRIM, GroupName, PartGroupMembership );
// Go through all primitives.
for ( int32 FaceIdx = 0; FaceIdx < PartGroupMembership.Num(); ++FaceIdx )
{
if ( PartGroupMembership[ FaceIdx ] > 0 )
{
// Add face.
AllFaceList.Add( FaceIdx );
// This face is a member of specified group.
if (FullVertexList.IsValidIndex(FaceIdx * 3 + 2 ) )
{
NewVertexList[ FaceIdx * 3 + 0 ] = FullVertexList[ FaceIdx * 3 + 0 ];
NewVertexList[ FaceIdx * 3 + 1 ] = FullVertexList[ FaceIdx * 3 + 1 ];
NewVertexList[ FaceIdx * 3 + 2 ] = FullVertexList[ FaceIdx * 3 + 2 ];
}
// Mark these vertex indices as used.
if ( AllVertexList.IsValidIndex( FaceIdx * 3 + 2 ) )
{
AllVertexList[ FaceIdx * 3 + 0 ] = 1;
AllVertexList[ FaceIdx * 3 + 1 ] = 1;
AllVertexList[ FaceIdx * 3 + 2 ] = 1;
}
// Mark this face as used.
if ( AllCollisionFaceIndices.IsValidIndex( FaceIdx ) )
AllCollisionFaceIndices[ FaceIdx ] = 1;
ProcessedWedges += 3;
}
}
return ProcessedWedges;
}
#if WITH_EDITOR
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;
}
#endif
int32
FHoudiniEngineUtils::CountUVSets( const FRawMesh & RawMesh )
{
int32 UVSetCount = 0;
#if WITH_EDITOR
for ( int32 TexCoordIdx = 0; TexCoordIdx < MAX_MESH_TEXTURE_COORDS; ++TexCoordIdx )
{
const TArray< FVector2D > & WedgeTexCoords = RawMesh.WedgeTexCoords[ TexCoordIdx ];
if ( WedgeTexCoords.Num() > 0 )
UVSetCount++;
}
#endif // WITH_EDITOR
return UVSetCount;
}
const FString
FHoudiniEngineUtils::GetStatusString( HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity )
{
int32 StatusBufferLength = 0;
FHoudiniApi::GetStatusStringBufLength( FHoudiniEngine::Get().GetSession(), status_type, verbosity, &StatusBufferLength );
if ( StatusBufferLength > 0 )
{
TArray< char > StatusStringBuffer;
StatusStringBuffer.SetNumZeroed( StatusBufferLength );
FHoudiniApi::GetStatusString( FHoudiniEngine::Get().GetSession(), status_type, &StatusStringBuffer[ 0 ], StatusBufferLength );
return FString( UTF8_TO_TCHAR( &StatusStringBuffer[ 0 ] ) );
}
return FString( TEXT( "" ));
}
bool
FHoudiniEngineUtils::ExtractUniqueMaterialIds(
const HAPI_AssetInfo & AssetInfo,
TSet< HAPI_NodeId > & MaterialIds,
TSet< HAPI_NodeId > & InstancerMaterialIds,
TMap< FHoudiniGeoPartObject, HAPI_NodeId > & InstancerMaterialMap )
{
// Empty passed incontainers.
MaterialIds.Empty();
InstancerMaterialIds.Empty();
InstancerMaterialMap.Empty();
TArray< HAPI_ObjectInfo > ObjectInfos;
if ( !HapiGetObjectInfos( AssetInfo.nodeId, ObjectInfos ) )
return false;
// Iterate through all objects.
for ( int32 ObjectIdx = 0; ObjectIdx < ObjectInfos.Num(); ++ObjectIdx )
{
// Retrieve object at this index.
const HAPI_ObjectInfo & ObjectInfo = ObjectInfos[ ObjectIdx ];
// Get Geo information.
HAPI_GeoInfo GeoInfo;
FHoudiniApi::GeoInfo_Init(&GeoInfo);
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( FHoudiniEngine::Get().GetSession(), ObjectInfo.nodeId, &GeoInfo ) )
continue;
// Iterate through all parts.
for ( int32 PartIdx = 0; PartIdx < GeoInfo.partCount; ++PartIdx )
{
// Get part information.
HAPI_PartInfo PartInfo;
FHoudiniApi::PartInfo_Init(&PartInfo);
FString PartName = TEXT( "" );
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId, PartIdx, &PartInfo ) )
continue;
// Retrieve material information for this geo part.
HAPI_Bool bSingleFaceMaterial = false;
bool bMaterialsFound = false;
if ( PartInfo.faceCount > 0 )
{
TArray< HAPI_NodeId > FaceMaterialIds;
FaceMaterialIds.SetNumUninitialized( PartInfo.faceCount );
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialNodeIdsOnFaces(
FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId, PartInfo.id,
&bSingleFaceMaterial, &FaceMaterialIds[ 0 ], 0, PartInfo.faceCount ) )
{
continue;
}
MaterialIds.Append( FaceMaterialIds );
}
else
{
// If this is an instancer, attempt to look up instancer material.
if ( ObjectInfo.isInstancer )
{
HAPI_NodeId InstanceMaterialId = -1;
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialNodeIdsOnFaces(
FHoudiniEngine::Get().GetSession(), GeoInfo.nodeId, PartInfo.id,
&bSingleFaceMaterial, &InstanceMaterialId, 0, 1 ) )
{
continue;
}
MaterialIds.Add( InstanceMaterialId );
if ( InstanceMaterialId != -1 )
{
FHoudiniGeoPartObject GeoPartObject( AssetInfo.nodeId, ObjectInfo.nodeId, GeoInfo.nodeId, PartInfo.id );
InstancerMaterialMap.Add( GeoPartObject, InstanceMaterialId );
InstancerMaterialIds.Add( InstanceMaterialId );
}
}
}
}
}
MaterialIds.Remove( -1 );
return true;
}
AHoudiniAssetActor *
FHoudiniEngineUtils::LocateClipboardActor( const AActor* IgnoreActor, const FString & ClipboardText )
{
const TCHAR * Paste = nullptr;
if ( ClipboardText.IsEmpty() )
{
FString PasteString;
#if WITH_EDITOR
FPlatformApplicationMisc::ClipboardPaste( PasteString );
#endif
Paste = *PasteString;
}
else
{
Paste = *ClipboardText;
}
AHoudiniAssetActor * HoudiniAssetActor = nullptr;
// Extract the Name/Label of the from the clipboard string ...
FString ActorName = TEXT( "" );
FString StrLine;
while ( FParse::Line( &Paste, StrLine ) )
{
StrLine = StrLine.TrimStart();
const TCHAR * Str = *StrLine;
FString ClassName;
if (StrLine.StartsWith(TEXT("Begin Actor")) && FParse::Value(Str, TEXT("Class="), ClassName))
{
if (ClassName == *AHoudiniAssetActor::StaticClass()->GetFName().ToString())
{
if (FParse::Value(Str, TEXT("Name="), ActorName))
break;
}
}
else if (StrLine.StartsWith(TEXT("ActorLabel")))
{
if (FParse::Value(Str, TEXT("ActorLabel="), ActorName))
break;
}
}
#if WITH_EDITOR
if (GEditor)
{
// Try to find the corresponding HoudiniAssetActor in the editor world
// to avoid finding "deleted" assets with the same name
UWorld* editorWorld = GEditor->GetEditorWorldContext().World();
for (TActorIterator<AHoudiniAssetActor> ActorItr(editorWorld); ActorItr; ++ActorItr)
{
if (*ActorItr != IgnoreActor && (ActorItr->GetActorLabel() == ActorName || ActorItr->GetName() == ActorName))
HoudiniAssetActor = *ActorItr;
}
}
#endif
return HoudiniAssetActor;
}
bool
FHoudiniEngineUtils::GetAssetNames(
UHoudiniAsset * HoudiniAsset, HAPI_AssetLibraryId & OutAssetLibraryId,
TArray< HAPI_StringHandle > & OutAssetNames )
{
OutAssetLibraryId = -1;
OutAssetNames.Empty();
if ( FHoudiniEngineUtils::IsInitialized() && HoudiniAsset && !HoudiniAsset->IsPendingKill() )
{
FString AssetFileName = HoudiniAsset->GetAssetFileName();
HAPI_Result Result = HAPI_RESULT_FAILURE;
HAPI_AssetLibraryId AssetLibraryId = -1;
int32 AssetCount = 0;
TArray< HAPI_StringHandle > AssetNames;
if ( FPaths::IsRelative( AssetFileName ) && ( FHoudiniEngine::Get().GetSession()->type != HAPI_SESSION_INPROCESS ) )
AssetFileName = FPaths::ConvertRelativePathToFull( AssetFileName );
if ( !AssetFileName.IsEmpty() && FPaths::FileExists( AssetFileName ) )
{
// We'll need to modify the file name for expanded .hda
FString FileExtension = FPaths::GetExtension( AssetFileName );
if ( FileExtension.Compare( TEXT( "hdalibrary" ), ESearchCase::IgnoreCase ) == 0 )
{
// the .hda directory is what we're interested in loading
AssetFileName = FPaths::GetPath( AssetFileName );
}
// File does exist, we can load asset from file.
std::string AssetFileNamePlain;
FHoudiniEngineUtils::ConvertUnrealString( AssetFileName, AssetFileNamePlain );
Result = FHoudiniApi::LoadAssetLibraryFromFile(
FHoudiniEngine::Get().GetSession(), AssetFileNamePlain.c_str(), true, &AssetLibraryId );
}
// Try to load the asset from memory if loading from file failed
if ( Result != HAPI_RESULT_SUCCESS )
{
// Expanded hdas cannot be loaded from Memory
FString FileExtension = FPaths::GetExtension( AssetFileName );
if ( FileExtension.Compare( TEXT( "hdalibrary" ), ESearchCase::IgnoreCase ) == 0 )
{
HOUDINI_LOG_ERROR( TEXT( "Error loading expanded Asset %s: source asset file not found." ), *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<const char *>( HoudiniAsset->GetAssetBytes() ),
HoudiniAsset->GetAssetBytesCount(), true, &AssetLibraryId );
}
}
if ( Result != HAPI_RESULT_SUCCESS )
{
HOUDINI_LOG_MESSAGE( TEXT( "Error loading asset library for %s: %s" ), *AssetFileName, *FHoudiniEngineUtils::GetErrorDescription() );
return false;
}
Result = FHoudiniApi::GetAvailableAssetCount( FHoudiniEngine::Get().GetSession(), AssetLibraryId, &AssetCount );
if ( Result != HAPI_RESULT_SUCCESS )
{
HOUDINI_LOG_MESSAGE( TEXT( "Error getting asset count for %s: %s" ), *AssetFileName, *FHoudiniEngineUtils::GetErrorDescription() );
return false;
}
if( AssetCount <= 0 )
{
HOUDINI_LOG_MESSAGE( TEXT( "Could not find an asset in library %s" ), *AssetFileName );
return false;
}
AssetNames.SetNumUninitialized( AssetCount );
Result = FHoudiniApi::GetAvailableAssets( FHoudiniEngine::Get().GetSession(), AssetLibraryId, &AssetNames[ 0 ], AssetCount );
if ( Result != HAPI_RESULT_SUCCESS )
{
HOUDINI_LOG_MESSAGE( TEXT( "Unable to retrieve asset names for %s: %s" ), *AssetFileName, *FHoudiniEngineUtils::GetErrorDescription() );
return false;
}
if ( !AssetCount )
{
HOUDINI_LOG_MESSAGE( TEXT( "No assets found within %s" ), *AssetFileName );
return false;
}
OutAssetLibraryId = AssetLibraryId;
OutAssetNames = AssetNames;
return true;
}
return false;
}
int32
FHoudiniEngineUtils::AddMeshSocketToList(
HAPI_NodeId AssetId, HAPI_NodeId ObjectId,
HAPI_NodeId GeoId, HAPI_PartId PartId,
TArray< FTransform >& AllSockets,
TArray< FString >& AllSocketsNames,
TArray< FString >& AllSocketsActors,
TArray< FString >& AllSocketsTags,
const bool& isPackedPrim )
{
// Attributes we are interested in.
// Position
TArray< float > Positions;
HAPI_AttributeInfo AttribInfoPositions;
FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions);
//FMemory::Memset< HAPI_AttributeInfo >( AttribInfoPositions, 0 );
// Rotation
bool bHasRotation = false;
TArray< float > Rotations;
HAPI_AttributeInfo AttribInfoRotations;
FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations);
// FMemory::Memset< HAPI_AttributeInfo >( AttribInfoRotations, 0 );
// Scale
bool bHasScale = false;
TArray< float > Scales;
HAPI_AttributeInfo AttribInfoScales;
FHoudiniApi::AttributeInfo_Init(&AttribInfoScales);
// FMemory::Memset< HAPI_AttributeInfo >( AttribInfoScales, 0 );
// When using socket groups, we can also get the sockets rotation from the normal
bool bHasNormals = false;
TArray< float > Normals;
HAPI_AttributeInfo AttribInfoNormals;
FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals);
// FMemory::Memset< HAPI_AttributeInfo >( AttribInfoNormals, 0 );
// Socket Name
bool bHasNames = false;
TArray< FString > Names;
HAPI_AttributeInfo AttribInfoNames;
FHoudiniApi::AttributeInfo_Init(&AttribInfoNames);
// FMemory::Memset< HAPI_AttributeInfo >( AttribInfoNames, 0 );
// Socket Actor
bool bHasActors = false;
TArray< FString > Actors;
HAPI_AttributeInfo AttribInfoActors;
FHoudiniApi::AttributeInfo_Init(&AttribInfoActors);
// FMemory::Memset< HAPI_AttributeInfo >( AttribInfoActors, 0 );
// Socket Tags
bool bHasTags = false;
TArray< FString > Tags;
HAPI_AttributeInfo AttribInfoTags;
FHoudiniApi::AttributeInfo_Init(&AttribInfoTags);
// FMemory::Memset< HAPI_AttributeInfo >( AttribInfoTags, 0 );
// Get runtime settings.
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
check( HoudiniRuntimeSettings );
float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION;
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
if ( HoudiniRuntimeSettings )
{
GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor;
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
}
// 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 )
{
FTransform currentSocketTransform;
FVector currentPosition = FVector::ZeroVector;
FVector currentScale = FVector( 1.0f, 1.0f, 1.0f );
FQuat currentRotation = FQuat::Identity;
if ( ImportAxis == HRSAI_Unreal )
{
if ( Positions.IsValidIndex( PointIdx * 3 + 2 ) )
{
currentPosition.X = Positions[ PointIdx * 3 ] * GeneratedGeometryScaleFactor;
currentPosition.Y = Positions[ PointIdx * 3 + 2 ] * GeneratedGeometryScaleFactor;
currentPosition.Z = Positions[ PointIdx * 3 + 1 ] * GeneratedGeometryScaleFactor;
}
if ( bHasScale && Scales.IsValidIndex( PointIdx * 3 + 2 ) )
{
currentScale.X = Scales[ PointIdx * 3 ];
currentScale.Y = Scales[ PointIdx * 3 + 2 ];
currentScale.Z = Scales[ PointIdx * 3 + 1 ];
}
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 );
}
}
else
{
if ( Positions.IsValidIndex( PointIdx * 3 + 2 ) )
{
currentPosition.X = Positions[ PointIdx * 3 ] * GeneratedGeometryScaleFactor;
currentPosition.Y = Positions[ PointIdx * 3 + 1 ] * GeneratedGeometryScaleFactor;
currentPosition.Z = Positions[ PointIdx * 3 + 2 ] * GeneratedGeometryScaleFactor;
}
if ( bHasScale && Scales.IsValidIndex( PointIdx * 3 + 2 ) )
{
currentScale.X = Scales[ PointIdx * 3 ];
currentScale.Y = Scales[ PointIdx * 3 + 1 ];
currentScale.Z = Scales[ PointIdx * 3 + 2 ];
}
if ( bHasRotation && Rotations.IsValidIndex( PointIdx * 4 + 3 ) )
{
currentRotation.X = Rotations[ PointIdx * 4 ];
currentRotation.Y = Rotations[ PointIdx * 4 + 1 ];
currentRotation.Z = Rotations[ PointIdx * 4 + 2 ];
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 + 1 ];
vNormal.Z = Normals[ PointIdx * 3 + 2 ];
if ( vNormal != FVector::ZeroVector )
currentRotation = FQuat::FindBetween( FVector::UpVector, vNormal );
}
}
FString currentName;
if ( bHasNames && Names.IsValidIndex( PointIdx ) )
currentName = Names[ PointIdx ];
FString currentActors;
if ( bHasActors && Actors.IsValidIndex( PointIdx ) )
currentActors = Actors[ PointIdx ];
FString currentTag;
if ( bHasTags && Tags.IsValidIndex( PointIdx ) )
currentTag = 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( 1.0f, 1.0f, 1.0f );
currentSocketTransform.SetLocation( currentPosition );
currentSocketTransform.SetRotation( currentRotation );
currentSocketTransform.SetScale3D( currentScale );
// We want to make sure we're not adding the same socket multiple times
int32 FoundIx = AllSockets.IndexOfByPredicate(
[ currentSocketTransform ]( const FTransform InTransform ){ return InTransform.Equals( currentSocketTransform); } );
if ( FoundIx >= 0 )
{
// If the transform, names and actors are identical, skip this duplicate
if ( ( AllSocketsNames[ FoundIx ] == currentName ) && ( AllSocketsActors[ FoundIx ] == currentActors ) )
return false;
}
AllSockets.Add( currentSocketTransform );
AllSocketsNames.Add( currentName );
AllSocketsActors.Add( currentActors );
AllSocketsTags.Add( currentTag );
FoundSocketCount++;
return true;
};
// Lambda function for reseting the arrays/attributes
auto ResetArraysAndAttr = [&]()
{
// Position
Positions.Empty();
FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions);
//FMemory::Memset< HAPI_AttributeInfo >( AttribInfoPositions, 0 );
// Rotation
bHasRotation = false;
Rotations.Empty();
FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations);
//FMemory::Memset< HAPI_AttributeInfo >( AttribInfoRotations, 0 );
// Scale
bHasScale = false;
Scales.Empty();
FHoudiniApi::AttributeInfo_Init(&AttribInfoScales);
//FMemory::Memset< HAPI_AttributeInfo >( AttribInfoScales, 0 );
// When using socket groups, we can also get the sockets rotation from the normal
bHasNormals = false;
Normals.Empty();
FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals);
//FMemory::Memset< HAPI_AttributeInfo >( AttribInfoNormals, 0 );
// Socket Name
bHasNames = false;
Names.Empty();
FHoudiniApi::AttributeInfo_Init(&AttribInfoNames);
//FMemory::Memset< HAPI_AttributeInfo >( AttribInfoNames, 0 );
// Socket Actor
bHasActors = false;
Actors.Empty();
FHoudiniApi::AttributeInfo_Init(&AttribInfoActors);
//FMemory::Memset< HAPI_AttributeInfo >( AttribInfoActors, 0 );
// Socket Tags
bHasTags = false;
Tags.Empty();
FHoudiniApi::AttributeInfo_Init(&AttribInfoTags);
//FMemory::Memset< HAPI_AttributeInfo >( AttribInfoTags, 0 );
};
//-------------------------------------------------------------------------
// FIND SOCKETS BY DETAIL ATTRIBUTES
//-------------------------------------------------------------------------
bool HasSocketAttributes = true;
int32 SocketIdx = 0;
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(
AssetId, ObjectId, 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(
AssetId, ObjectId, 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(
AssetId, ObjectId, 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(
AssetId, ObjectId, GeoId, PartId,
TCHAR_TO_ANSI(*SocketNameAttr), AttribInfoNames, Names ) )
bHasNames = true;
// Retrieve mesh socket actor.
FString SocketActorAttr = SocketAttrPrefix + TEXT("_actor");
if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString(
AssetId, ObjectId, GeoId, PartId,
TCHAR_TO_ANSI(*SocketActorAttr), AttribInfoActors, Actors ) )
bHasActors = true;
// Retrieve mesh socket tags.
FString SocketTagAttr = SocketAttrPrefix + TEXT("_tag");
if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString(
AssetId, ObjectId, 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++;
}
//-------------------------------------------------------------------------
// FIND SOCKETS BY POINT GROUPS
//-------------------------------------------------------------------------
// Get object / geo group memberships for primitives.
TArray< FString > GroupNames;
if ( !FHoudiniEngineUtils::HapiGetGroupNames(
AssetId, ObjectId, GeoId, PartId, HAPI_GROUPTYPE_POINT, GroupNames, isPackedPrim ) )
{
HOUDINI_LOG_MESSAGE( TEXT( "GetMeshSocketList: Object [%d] non-fatal error reading group names" ), ObjectId );
}
// 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( TEXT( HAPI_UNREAL_GROUP_MESH_SOCKETS ), ESearchCase::IgnoreCase )
|| GroupName.StartsWith( TEXT( HAPI_UNREAL_GROUP_MESH_SOCKETS_OLD ), ESearchCase::IgnoreCase ))
{
bHasSocketGroup = true;
break;
}
}
if ( !bHasSocketGroup )
return FoundSocketCount;
// Reset the data arrays and attributes
ResetArraysAndAttr();
// Retrieve position data.
if ( !FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
AssetId, ObjectId, GeoId, PartId,
HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, Positions ) )
return false;
// Retrieve rotation data.
if ( FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
AssetId, ObjectId, GeoId, PartId,
HAPI_UNREAL_ATTRIB_ROTATION, AttribInfoRotations, Rotations ) )
bHasRotation = true;
// Retrieve normal data.
if ( FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
AssetId, ObjectId, GeoId, PartId,
HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, Normals ) )
bHasNormals = true;
// Retrieve scale data.
if ( FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
AssetId, ObjectId, GeoId, PartId,
HAPI_UNREAL_ATTRIB_SCALE, AttribInfoScales, Scales ) )
bHasScale = true;
// Retrieve mesh socket names.
if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString(
AssetId, ObjectId, GeoId, PartId,
HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, AttribInfoNames, Names ) )
bHasNames = true;
else if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString(
AssetId, ObjectId, GeoId, PartId,
HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD, AttribInfoNames, Names ) )
bHasNames = true;
// Retrieve mesh socket actor.
if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString(
AssetId, ObjectId, GeoId, PartId,
HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR, AttribInfoActors, Actors ) )
bHasActors = true;
else if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString(
AssetId, ObjectId, GeoId, PartId,
HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD, AttribInfoActors, Actors ) )
bHasActors = true;
// Retrieve mesh socket tags.
if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString(
AssetId, ObjectId, GeoId, PartId,
HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, AttribInfoTags, Tags ) )
bHasTags = true;
else if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString(
AssetId, ObjectId, 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 ( TEXT ( HAPI_UNREAL_GROUP_MESH_SOCKETS ) , ESearchCase::IgnoreCase )
&& !GroupName.StartsWith ( TEXT ( HAPI_UNREAL_GROUP_MESH_SOCKETS_OLD ) , ESearchCase::IgnoreCase ) )
continue;
TArray< int32 > PointGroupMembership;
FHoudiniEngineUtils::HapiGetGroupMembership(
AssetId, ObjectId, GeoId, PartId,
HAPI_GROUPTYPE_POINT, GroupName, PointGroupMembership );
// Go through all primitives.
for ( int32 PointIdx = 0; PointIdx < PointGroupMembership.Num(); ++PointIdx )
{
if ( PointGroupMembership[ PointIdx ] == 0 )
continue;
// Add the corresponding socket to the array
AddSocketToArray( PointIdx );
}
}
return FoundSocketCount;
}
bool
FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(
UStaticMesh* StaticMesh,
FHoudiniGeoPartObject& HoudiniGeoPartObject,
TArray< FTransform >& AllSockets,
TArray< FString >& AllSocketsNames,
TArray< FString >& AllSocketsActors,
TArray< FString >& AllSocketsTags )
{
if ( !StaticMesh || StaticMesh->IsPendingKill() )
return false;
if ( AllSockets.Num() <= 0 )
return false;
// Remove the sockets from the previous cook!
if ( !HoudiniGeoPartObject.bHasSocketBeenAdded )
StaticMesh->Sockets.Empty();
for ( int32 nSocket = 0; nSocket < AllSockets.Num(); nSocket++ )
{
// Create a new Socket
UStaticMeshSocket* Socket = NewObject<UStaticMeshSocket>( StaticMesh );
if ( !Socket || Socket->IsPendingKill() )
continue;
Socket->RelativeLocation = AllSockets[ nSocket ].GetLocation();
Socket->RelativeRotation = FRotator(AllSockets[ nSocket ].GetRotation());
Socket->RelativeScale = AllSockets[ nSocket ].GetScale3D();
if ( AllSocketsNames.IsValidIndex( nSocket ) && !AllSocketsNames[ nSocket ].IsEmpty() )
{
Socket->SocketName = FName( *AllSocketsNames[ nSocket ] );
}
else
{
// Having sockets with empty names can lead to various issues, so we'll create one now
FString SocketName = TEXT("Socket ") + FString::FromInt( nSocket );
Socket->SocketName = FName( *SocketName );
}
// Socket Tag
FString Tag;
if ( AllSocketsTags.IsValidIndex( nSocket ) && !AllSocketsTags[ nSocket ].IsEmpty() )
Tag = AllSocketsTags[ nSocket ];
// The actor will be stored temporarily in the socket's Tag as we need a StaticMeshComponent to add an actor to the socket
if ( AllSocketsActors.IsValidIndex( nSocket ) && !AllSocketsActors[ nSocket ].IsEmpty() )
Tag += TEXT("|") + AllSocketsActors[ nSocket ];
Socket->Tag = Tag;
StaticMesh->Sockets.Add( Socket );
}
// We don't want these new socket to be removed until next cook
HoudiniGeoPartObject.bHasSocketBeenAdded = true;
return true;
}
bool
FHoudiniEngineUtils::AddAggregateCollisionGeometryToStaticMesh(
UStaticMesh* StaticMesh,
FHoudiniGeoPartObject& HoudiniGeoPartObject,
FKAggregateGeom& AggregateCollisionGeo )
{
if ( !StaticMesh || StaticMesh->IsPendingKill() )
return false;
UBodySetup * BodySetup = StaticMesh->BodySetup;
if ( !BodySetup || BodySetup->IsPendingKill() )
return false;
// Do we need to remove the old collisions from the previous cook
if ( !HoudiniGeoPartObject.bHasCollisionBeenAdded )
BodySetup->RemoveSimpleCollision();
BodySetup->AddCollisionFrom( AggregateCollisionGeo );
BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault;
BodySetup->ClearPhysicsMeshes();
BodySetup->InvalidatePhysicsData();
#if WITH_EDITOR
RefreshCollisionChange( *StaticMesh );
#endif
// This geo part will have to be considered as rendered collision
if ( !HoudiniGeoPartObject.bIsCollidable )
HoudiniGeoPartObject.bIsRenderCollidable = true;
// We don't want these collisions to be removed before the next cook
HoudiniGeoPartObject.bHasCollisionBeenAdded = true;
// Clean the added collisions
AggregateCollisionGeo.EmptyElements();
return true;
}
bool
FHoudiniEngineUtils::AddActorsToMeshSocket( UStaticMeshSocket* Socket, UStaticMeshComponent* StaticMeshComponent )
{
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<FString> 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 );
if ( ActorStringArray.Num() <= 0 )
return false;
#if WITH_EDITOR
// 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();
UWorld* editorWorld = StaticMeshComponent->GetOwner() ? StaticMeshComponent->GetOwner()->GetWorld() : nullptr;
if ( !editorWorld || editorWorld->IsPendingKill() )
return false;
for ( TActorIterator<AActor> 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;
Socket->AttachActor( Actor, StaticMeshComponent );
}
}
#endif
return true;
}
int32
FHoudiniEngineUtils::GetUPropertyAttributeList( const FHoudiniGeoPartObject& GeoPartObject, TArray< UGenericAttribute >& AllUProps )
{
// Get the detail uprop attributes
int nUPropsCount = FHoudiniEngineUtils::GetGenericAttributeList( GeoPartObject, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, AllUProps, HAPI_ATTROWNER_DETAIL );
// Then the primitive uprop attributes
nUPropsCount += FHoudiniEngineUtils::GetGenericAttributeList( GeoPartObject, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, AllUProps, HAPI_ATTROWNER_PRIM );
return nUPropsCount;
}
int32
FHoudiniEngineUtils::GetGenericAttributeList(
const FHoudiniGeoPartObject& GeoPartObject,
const FString& GenericAttributePrefix,
TArray< UGenericAttribute >& AllUProps,
const HAPI_AttributeOwner& AttributeOwner,
int32 PrimitiveIndex )
{
HAPI_NodeId NodeId = GeoPartObject.HapiGeoGetNodeId();
HAPI_PartInfo PartInfo;
FHoudiniApi::PartInfo_Init(&PartInfo);
if ( !GeoPartObject.HapiPartGetInfo( PartInfo ) )
return 0;
HAPI_PartId PartId = GeoPartObject.GetPartId();
int32 nUPropCount = 0;
// Get All attribute names for that part
int32 nAttribCount = PartInfo.attributeCounts[ AttributeOwner ];
TArray<HAPI_StringHandle> AttribNameSHArray;
AttribNameSHArray.SetNum( nAttribCount );
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeNames(
FHoudiniEngine::Get().GetSession(),
NodeId, PartInfo.id, AttributeOwner,
AttribNameSHArray.GetData(), nAttribCount ) )
return 0;
// Since generic attributes can be on primitives, we may have to identify if a split occured during the mesh creation.
// If the primitive index was not specified, we need to a suitable primitive corresponding to that split
bool HandleSplit = false;
int PrimIndexForSplit = -1;
if ( AttributeOwner != HAPI_ATTROWNER_DETAIL )
{
if ( PrimitiveIndex != -1 )
{
// The index has already been specified so we'll use it
HandleSplit = true;
PrimIndexForSplit = PrimitiveIndex;
}
else if ( !GeoPartObject.SplitName.IsEmpty() && ( GeoPartObject.SplitName != TEXT("main_geo") ) )
{
HandleSplit = true;
// Since the meshes have been split, we need to find a primitive that belongs to the proper group
// so we can read the proper value for its generic attribute
TArray< int32 > PartGroupMembership;
FHoudiniEngineUtils::HapiGetGroupMembership(
GeoPartObject.AssetId, GeoPartObject.GetObjectId(), GeoPartObject.GetGeoId(), GeoPartObject.GetPartId(),
HAPI_GROUPTYPE_PRIM, GeoPartObject.SplitName, PartGroupMembership );
for ( int32 n = 0; n < PartGroupMembership.Num(); n++ )
{
if ( PartGroupMembership[ n ] > 0 )
{
PrimIndexForSplit = n;
break;
}
}
}
if ( PrimIndexForSplit < 0 )
PrimIndexForSplit = 0;
}
for ( int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx )
{
FString HapiString = TEXT("");
FHoudiniEngineString HoudiniEngineString( AttribNameSHArray[ Idx ] );
HoudiniEngineString.ToFString( HapiString );
if ( HapiString.StartsWith( GenericAttributePrefix, ESearchCase::IgnoreCase ) )
{
UGenericAttribute CurrentUProperty;
// Extract the name of the UProperty from the attribute name
CurrentUProperty.AttributeName = HapiString.Right( HapiString.Len() - GenericAttributePrefix.Len() );
// Get the Attribute Info
HAPI_AttributeInfo AttribInfo;
FHoudiniApi::AttributeInfo_Init(&AttribInfo);
//FMemory::Memzero< HAPI_AttributeInfo >( AttribInfo );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo(
FHoudiniEngine::Get().GetSession(),
NodeId, PartId, TCHAR_TO_UTF8( *HapiString ),
AttributeOwner, &AttribInfo ), false );
// Get the attribute type and tuple size
CurrentUProperty.AttributeType = AttribInfo.storage;
CurrentUProperty.AttributeCount = AttribInfo.count;
CurrentUProperty.AttributeTupleSize = AttribInfo.tupleSize;
if ( CurrentUProperty.AttributeType == HAPI_STORAGETYPE_FLOAT64 )
{
// Initialize the value array
CurrentUProperty.DoubleValues.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize );
// Get the value(s)
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeFloat64Data(
FHoudiniEngine::Get().GetSession(),
NodeId, PartId, TCHAR_TO_UTF8( *HapiString ) ,&AttribInfo,
0, CurrentUProperty.DoubleValues.GetData(), 0, AttribInfo.count ), false );
if ( HandleSplit )
{
// For split primitives, we'll keep only one value from the proper split prim
TArray<double> SplitValues;
CurrentUProperty.GetDoubleTuple( SplitValues, PrimIndexForSplit );
CurrentUProperty.DoubleValues.Empty();
for ( int32 n = 0; n < SplitValues.Num(); n++ )
CurrentUProperty.DoubleValues.Add( SplitValues[ n ] );
CurrentUProperty.AttributeCount = 1;
}
}
else if ( CurrentUProperty.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_FLOAT )
{
// Initialize the value array
TArray< float > FloatValues;
FloatValues.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize );
// Get the value(s)
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
NodeId, PartId, TCHAR_TO_UTF8( *HapiString ) ,&AttribInfo,
0, FloatValues.GetData(), 0, AttribInfo.count ), false );
// Convert them to double
CurrentUProperty.DoubleValues.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize );
for ( int32 n = 0; n < FloatValues.Num(); n++ )
CurrentUProperty.DoubleValues[ n ] = (double)FloatValues[ n ];
}
else if ( CurrentUProperty.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_INT64 )
{
// Initialize the value array
CurrentUProperty.IntValues.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize );
// Get the value(s)
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInt64Data(
FHoudiniEngine::Get().GetSession(),
NodeId, PartId, TCHAR_TO_UTF8( *HapiString) ,&AttribInfo,
0, CurrentUProperty.IntValues.GetData(), 0, AttribInfo.count ), false );
}
else if ( CurrentUProperty.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_INT )
{
// Initialize the value array
TArray< int32 > IntValues;
IntValues.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize );
// Get the value(s)
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeIntData(
FHoudiniEngine::Get().GetSession(),
NodeId, PartId, TCHAR_TO_UTF8( *HapiString) ,&AttribInfo,
0, IntValues.GetData(), 0, AttribInfo.count ), false );
// Convert them to int64
CurrentUProperty.IntValues.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize );
for( int32 n = 0; n < IntValues.Num(); n++ )
CurrentUProperty.IntValues[ n ] = (int64)IntValues[ n ];
}
else if ( CurrentUProperty.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING )
{
// Initialize a string handle array
TArray< HAPI_StringHandle > HapiSHArray;
HapiSHArray.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize );
// Get the string handle(s)
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeStringData(
FHoudiniEngine::Get().GetSession(),
NodeId, PartId, TCHAR_TO_UTF8( *HapiString) ,&AttribInfo,
HapiSHArray.GetData(), 0, AttribInfo.count ), false );
// Convert them to FString
CurrentUProperty.StringValues.SetNumZeroed( AttribInfo.count * AttribInfo.tupleSize );
for( int32 IdxSH = 0; IdxSH < HapiSHArray.Num(); IdxSH++ )
{
FString CurrentString;
FHoudiniEngineString HEngineString( HapiSHArray[ IdxSH ] );
HEngineString.ToFString( CurrentString );
CurrentUProperty.StringValues[ IdxSH ] = CurrentString;
}
}
else
{
// Unsupported type, skipping!
continue;
}
// Handling Split
// For split primitives, we'll keep only one value from the proper split prim
if ( HandleSplit )
{
if ( ( CurrentUProperty.AttributeType == HAPI_STORAGETYPE_FLOAT64 )
|| ( CurrentUProperty.AttributeType == HAPI_STORAGETYPE_FLOAT ) )
{
TArray< double > SplitValues;
CurrentUProperty.GetDoubleTuple( SplitValues, PrimIndexForSplit );
CurrentUProperty.DoubleValues.Empty();
for ( int32 n = 0; n < SplitValues.Num(); n++ )
CurrentUProperty.DoubleValues.Add( SplitValues[ n ] );
}
else if ( ( CurrentUProperty.AttributeType == HAPI_STORAGETYPE_INT64 )
|| ( CurrentUProperty.AttributeType == HAPI_STORAGETYPE_INT ) )
{
TArray< int64 > SplitValues;
CurrentUProperty.GetIntTuple( SplitValues, PrimIndexForSplit );
CurrentUProperty.IntValues.Empty();
for ( int32 n = 0; n < SplitValues.Num(); n++ )
CurrentUProperty.IntValues.Add( SplitValues[ n ] );
}
else if ( CurrentUProperty.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING )
{
TArray< FString > SplitValues;
CurrentUProperty.GetStringTuple( SplitValues, PrimIndexForSplit );
CurrentUProperty.StringValues.Empty();
for ( int32 n = 0; n < SplitValues.Num(); n++ )
CurrentUProperty.StringValues.Add( SplitValues[ n ] );
}
CurrentUProperty.AttributeCount = 1;
}
// We can add the UPropertyAttribute to the array
AllUProps.Add( CurrentUProperty );
nUPropCount++;
}
}
return nUPropCount;
}
void
FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject(
UObject* MeshComponent, const FHoudiniGeoPartObject& HoudiniGeoPartObject )
{
if ( !MeshComponent || MeshComponent->IsPendingKill() )
return;
// Get the list of uproperties to modify from the geopartobject's attributes
TArray< UGenericAttribute > UPropertiesAttributesToModify;
if ( !FHoudiniEngineUtils::GetUPropertyAttributeList( HoudiniGeoPartObject, UPropertiesAttributesToModify ) )
return;
// Iterate over the Found UProperty attributes
for ( int32 nAttributeIdx = 0; nAttributeIdx < UPropertiesAttributesToModify.Num(); nAttributeIdx++ )
{
// Get the current Uproperty Attribute
UGenericAttribute CurrentPropAttribute = UPropertiesAttributesToModify[ nAttributeIdx ];
FString CurrentUPropertyName = CurrentPropAttribute.AttributeName;
if ( CurrentUPropertyName.IsEmpty() )
continue;
// Some UProperties need to be modified manually...
if ( CurrentUPropertyName == "CollisionProfileName" )
{
UPrimitiveComponent* PC = Cast< UPrimitiveComponent >( MeshComponent );
if ( PC && !PC->IsPendingKill() )
{
FString StringValue = CurrentPropAttribute.GetStringValue();
FName Value = FName( *StringValue );
PC->SetCollisionProfileName( Value );
continue;
}
}
else if ( CurrentUPropertyName == "Visible" )
{
USceneComponent* SC = Cast< USceneComponent >(MeshComponent);
if (SC && !SC->IsPendingKill())
{
bool bVal = CurrentPropAttribute.GetBoolValue();
SC->SetVisibility(bVal);
continue;
}
}
// Handle Component Tags manually here
if ( CurrentUPropertyName.Contains("Tags") )
{
UActorComponent* AC = Cast< UActorComponent >( MeshComponent );
if ( AC && !AC->IsPendingKill() )
{
for ( int nIdx = 0; nIdx < CurrentPropAttribute.AttributeCount; nIdx++ )
{
FName NameAttr = FName( *CurrentPropAttribute.GetStringValue( nIdx ) );
if ( !AC->ComponentTags.Contains( NameAttr ) )
AC->ComponentTags.Add( NameAttr );
}
continue;
}
}
// Try to find the corresponding UProperty
FProperty* FoundProperty = nullptr;
void* StructContainer = nullptr;
UObject* FoundPropertyObject = nullptr;
if ( !FindUPropertyAttributesOnObject( MeshComponent, CurrentPropAttribute, FoundProperty, FoundPropertyObject, StructContainer ) )
continue;
if ( !ModifyUPropertyValueOnObject( FoundPropertyObject, CurrentPropAttribute, FoundProperty, StructContainer ) )
continue;
// Sucess!
FString ClassName = MeshComponent->GetClass() ? MeshComponent->GetClass()->GetName() : TEXT("Object");
FString ObjectName = MeshComponent->GetName();
// Couldn't find or modify the Uproperty!
HOUDINI_LOG_MESSAGE( TEXT( "Modified FProperty %s on %s named %s" ), *CurrentUPropertyName, *ClassName, * ObjectName );
}
}
/*
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<UStructProperty>( Property );
if ( NestedStruct && TryToFindInStructProperty( Object, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) )
return true;
UArrayProperty* ArrayProp = Cast<UArrayProperty>( 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<UStructProperty>( Property );
if ( NestedStruct && TryToFindInStructProperty( ArrayProperty, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) )
return true;
UArrayProperty* ArrayProp = Cast<UArrayProperty>( Property );
if ( ArrayProp && TryToFindInArrayProperty( ArrayProperty, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) )
return true;
}
return false;
}
*/
bool FHoudiniEngineUtils::FindUPropertyAttributesOnObject(
UObject* ParentObject, const UGenericAttribute& UPropertiesToFind,
FProperty*& FoundProperty, UObject*& FoundPropertyObject, void*& StructContainer )
{
#if WITH_EDITOR
if ( !ParentObject || ParentObject->IsPendingKill() )
return false;
// Get the name of the uprop we're looking for
FString CurrentUPropertyName = UPropertiesToFind.AttributeName;
if ( CurrentUPropertyName.IsEmpty() )
return false;
UClass* MeshClass = ParentObject->GetClass();
if ( !MeshClass || MeshClass->IsPendingKill() )
return false;
// Set the result pointer to null
StructContainer = nullptr;
FoundProperty = nullptr;
FoundPropertyObject = ParentObject;
bool bPropertyHasBeenFound = false;
/*
// Try to find the uprop using a TPropValueIterator??
for ( TPropertyValueIterator<UProperty> It( MeshClass, ParentObject, EPropertyValueIteratorFlags::FullRecursion ); It; ++It )
{
UProperty* CurrentProperty = It.Key();
void* CurrentPropertyValue = (void*)It.Value();
UObject* ObjectValue = *((UObject**)CurrentPropertyValue);
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( CurrentUPropertyName ) || DisplayName.Contains( CurrentUPropertyName ) )
{
FoundProperty = CurrentProperty;
// If it's an equality, we dont need to keep searching
if ( ( Name == CurrentUPropertyName ) || ( DisplayName == CurrentUPropertyName ) )
{
bPropertyHasBeenFound = true;
break;
}
}
}
if ( bPropertyHasBeenFound )
return true;
*/
// Iterate manually on the properties, in order to handle structProperties correctly
for ( TFieldIterator< FProperty > PropIt( MeshClass, 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( CurrentUPropertyName ) || DisplayName.Contains( CurrentUPropertyName ) )
{
FoundProperty = CurrentProperty;
// If it's an equality, we dont need to keep searching
if ( ( Name == CurrentUPropertyName ) || ( DisplayName == CurrentUPropertyName ) )
{
bPropertyHasBeenFound = true;
break;
}
}
/*
// StructProperty need to be a nested struct
if (FStructProperty* StructProperty = CastFiled< FStructProperty >(CurrentProperty))
bPropertyHasBeenFound = TryToFindInStructProperty(MeshComponent, CurrentUPropertyName, StructProperty, FoundProperty, StructContainer);
else if (FArrayProperty* ArrayProperty = CastField< FArrayProperty >(CurrentProperty))
bPropertyHasBeenFound = TryToFindInArrayProperty(MeshComponent, CurrentUPropertyName, ArrayProperty, FoundProperty, StructContainer);
*/
// StructProperty need to be a nested struct
FStructProperty* StructProperty = CastField< FStructProperty >(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< FProperty > 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( CurrentUPropertyName ) || DisplayName.Contains( CurrentUPropertyName ) )
{
// 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 >( ParentObject, 0 );
// If it's an equality, we dont need to keep searching
if ( ( Name == CurrentUPropertyName ) || ( DisplayName == CurrentUPropertyName ) )
{
bPropertyHasBeenFound = true;
break;
}
}
}
}
if ( bPropertyHasBeenFound )
break;
}
if ( bPropertyHasBeenFound )
return true;
// Try with FindField??
if ( !FoundProperty )
FoundProperty = FindFProperty<FProperty>( MeshClass, *CurrentUPropertyName );
// Try with FindPropertyByName ??
if ( !FoundProperty )
FoundProperty = MeshClass->FindPropertyByName( *CurrentUPropertyName );
// We found the UProperty we were looking for
if ( FoundProperty )
return true;
// Handle common properties nested in classes
// Static Meshes
UStaticMesh* SM = Cast< UStaticMesh >( ParentObject );
if ( SM && !SM->IsPendingKill() )
{
if ( SM->BodySetup && FindUPropertyAttributesOnObject(
SM->BodySetup, UPropertiesToFind, FoundProperty, FoundPropertyObject, StructContainer ) )
{
return true;
}
if ( SM->AssetImportData && FindUPropertyAttributesOnObject(
SM->AssetImportData, UPropertiesToFind, FoundProperty, FoundPropertyObject, StructContainer ) )
{
return true;
}
if ( SM->NavCollision && FindUPropertyAttributesOnObject(
SM->NavCollision, UPropertiesToFind, FoundProperty, FoundPropertyObject, StructContainer ) )
{
return true;
}
}
// We found the UProperty we were looking for
if ( FoundProperty )
return true;
#endif
return false;
}
bool FHoudiniEngineUtils::ModifyUPropertyValueOnObject(
UObject* MeshComponent, UGenericAttribute CurrentPropAttribute,
FProperty* FoundProperty, void * StructContainer )
{
if ( !MeshComponent || MeshComponent->IsPendingKill() || !FoundProperty )
return false;
FProperty* InnerProperty = FoundProperty;
int32 NumberOfProperties = 1;
FArrayProperty* ArrayProperty = CastField< FArrayProperty >(FoundProperty);
if ( ArrayProperty )
{
InnerProperty = ArrayProperty->Inner;
NumberOfProperties = ArrayProperty->ArrayDim;
// Do we need to add values to the array?
FScriptArrayHelper_InContainer ArrayHelper( ArrayProperty, CurrentPropAttribute.GetData() );
//ArrayHelper.ExpandForIndex( CurrentPropAttribute.AttributeTupleSize - 1 );
if ( CurrentPropAttribute.AttributeTupleSize > NumberOfProperties )
{
ArrayHelper.Resize( CurrentPropAttribute.AttributeTupleSize );
NumberOfProperties = CurrentPropAttribute.AttributeTupleSize;
}
}
for ( int32 nPropIdx = 0; nPropIdx < NumberOfProperties; nPropIdx++ )
{
if ( FFloatProperty* FloatProperty = CastField< FFloatProperty >( InnerProperty ) )
{
// FLOAT PROPERTY
if ( CurrentPropAttribute.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING )
{
FString Value = CurrentPropAttribute.GetStringValue( nPropIdx );
void * ValuePtr = StructContainer ?
InnerProperty->ContainerPtrToValuePtr< FString >( (uint8 *)StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx );
if ( ValuePtr )
FloatProperty->SetNumericPropertyValueFromString( ValuePtr, *Value );
}
else
{
double Value = CurrentPropAttribute.GetDoubleValue( nPropIdx );
void * ValuePtr = StructContainer ?
InnerProperty->ContainerPtrToValuePtr< float >( (uint8 *)StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< float >( MeshComponent, nPropIdx );
if ( ValuePtr )
FloatProperty->SetFloatingPointPropertyValue( ValuePtr, Value );
}
}
else if ( FIntProperty* IntProperty = CastField< FIntProperty >( InnerProperty ) )
{
// INT PROPERTY
if ( CurrentPropAttribute.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING )
{
FString Value = CurrentPropAttribute.GetStringValue( nPropIdx );
void * ValuePtr = StructContainer ?
InnerProperty->ContainerPtrToValuePtr< FString >( (uint8 *)StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx );
if ( ValuePtr )
IntProperty->SetNumericPropertyValueFromString( ValuePtr, *Value );
}
else
{
int64 Value = CurrentPropAttribute.GetIntValue( nPropIdx );
void * ValuePtr = StructContainer ?
InnerProperty->ContainerPtrToValuePtr< int64 >( (uint8 *)StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< int64 >( MeshComponent, nPropIdx );
if ( ValuePtr )
IntProperty->SetIntPropertyValue( ValuePtr, Value );
}
}
else if ( FBoolProperty* BoolProperty = CastField< FBoolProperty >( InnerProperty ) )
{
// BOOL PROPERTY
bool Value = CurrentPropAttribute.GetBoolValue( nPropIdx );
void * ValuePtr = StructContainer ?
InnerProperty->ContainerPtrToValuePtr< bool >( (uint8*)StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< bool >( MeshComponent, nPropIdx );
if ( ValuePtr )
BoolProperty->SetPropertyValue( ValuePtr, Value );
}
else if ( FStrProperty* StringProperty = CastField< FStrProperty >( InnerProperty ) )
{
// STRING PROPERTY
FString Value = CurrentPropAttribute.GetStringValue( nPropIdx );
void * ValuePtr = StructContainer ?
InnerProperty->ContainerPtrToValuePtr< FString >( (uint8*)StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx );
if ( ValuePtr )
StringProperty->SetPropertyValue( ValuePtr, Value );
}
else if ( FNumericProperty *NumericProperty = CastField< FNumericProperty >( InnerProperty ) )
{
// NUMERIC PROPERTY
if ( CurrentPropAttribute.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING )
{
FString Value = CurrentPropAttribute.GetStringValue( nPropIdx );
void * ValuePtr = StructContainer ?
InnerProperty->ContainerPtrToValuePtr< FString >( (uint8 *)StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx );
if ( ValuePtr )
NumericProperty->SetNumericPropertyValueFromString( ValuePtr, *Value );
}
else if ( NumericProperty->IsInteger() )
{
int64 Value = CurrentPropAttribute.GetIntValue( nPropIdx );
void * ValuePtr = StructContainer ?
InnerProperty->ContainerPtrToValuePtr< int64 >( (uint8*)StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< int64 >( MeshComponent, nPropIdx );
if ( ValuePtr )
NumericProperty->SetIntPropertyValue(ValuePtr, (int64)Value );
}
else if ( NumericProperty->IsFloatingPoint() )
{
double Value = CurrentPropAttribute.GetDoubleValue( nPropIdx );
void * ValuePtr = StructContainer ?
InnerProperty->ContainerPtrToValuePtr< float >( (uint8 *)StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< float >( MeshComponent, nPropIdx );
if ( ValuePtr )
NumericProperty->SetFloatingPointPropertyValue( ValuePtr, Value );
}
else
{
// Numeric Property was found, but is of an unsupported type
HOUDINI_LOG_MESSAGE( TEXT("Unsupported Numeric UProperty") );
}
}
else if ( FNameProperty* NameProperty = CastField< FNameProperty >( InnerProperty ) )
{
// NAME PROPERTY
FString StringValue = CurrentPropAttribute.GetStringValue( nPropIdx );
FName Value = FName( *StringValue );
void * ValuePtr = StructContainer ?
InnerProperty->ContainerPtrToValuePtr< FName >( (uint8*)StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< FName >( MeshComponent, nPropIdx );
if ( ValuePtr )
NameProperty->SetPropertyValue( ValuePtr, Value );
}
else if ( FStructProperty* StructProperty = CastField< FStructProperty >( InnerProperty ) )
{
// STRUCT PROPERTY
const FName PropertyName = StructProperty->Struct->GetFName();
if ( PropertyName == NAME_Vector )
{
FVector* PropertyValue = StructContainer ?
InnerProperty->ContainerPtrToValuePtr< FVector >( (uint8 *)StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< FVector >( MeshComponent, nPropIdx );
if ( PropertyValue )
{
// Found a vector property, fill it with the 3 tuple values
PropertyValue->X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 0 );
PropertyValue->Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 1 );
PropertyValue->Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 2 );
}
}
else if ( PropertyName == NAME_Transform )
{
FTransform* PropertyValue = StructContainer ?
InnerProperty->ContainerPtrToValuePtr< FTransform >( (uint8 *)StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< FTransform >( MeshComponent, nPropIdx );
if ( PropertyValue )
{
// Found a transform property fill it with the attribute tuple values
FVector Translation;
Translation.X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 0 );
Translation.Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 1 );
Translation.Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 2 );
FQuat Rotation;
Rotation.W = CurrentPropAttribute.GetDoubleValue( nPropIdx + 3 );
Rotation.X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 4 );
Rotation.Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 5 );
Rotation.Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 6 );
FVector Scale;
Scale.X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 7 );
Scale.Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 8 );
Scale.Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 9 );
PropertyValue->SetTranslation( Translation );
PropertyValue->SetRotation( Rotation );
PropertyValue->SetScale3D( Scale );
}
}
else if ( PropertyName == NAME_Color )
{
FColor* PropertyValue = StructContainer ?
InnerProperty->ContainerPtrToValuePtr< FColor >( (uint8 *)StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< FColor >( MeshComponent, nPropIdx );
if ( PropertyValue )
{
PropertyValue->R = (int8)CurrentPropAttribute.GetIntValue( nPropIdx );
PropertyValue->G = (int8)CurrentPropAttribute.GetIntValue( nPropIdx + 1 );
PropertyValue->B = (int8)CurrentPropAttribute.GetIntValue( nPropIdx + 2 );
if ( CurrentPropAttribute.AttributeTupleSize == 4 )
PropertyValue->A = (int8)CurrentPropAttribute.GetIntValue( nPropIdx + 3 );
}
}
else if ( PropertyName == NAME_LinearColor )
{
FLinearColor* PropertyValue = StructContainer ?
InnerProperty->ContainerPtrToValuePtr< FLinearColor >( (uint8 *)StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< FLinearColor >( MeshComponent, nPropIdx );
if ( PropertyValue )
{
PropertyValue->R = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx );
PropertyValue->G = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx + 1 );
PropertyValue->B = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx + 2 );
if ( CurrentPropAttribute.AttributeTupleSize == 4 )
PropertyValue->A = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx + 3 );
}
}
}
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, *CurrentPropAttribute.AttributeName );
return false;
}
}
return true;
}
/*
void
FHoudiniEngineUtils::ApplyUPropertyAttributesOnObject(
UObject* MeshComponent, const TArray< UGenericAttribute >& UPropertiesToModify )
{
#if WITH_EDITOR
if ( !MeshComponent )
return;
int nUPropsCount = UPropertiesToModify.Num();
if ( nUPropsCount <= 0 )
return;
// MeshComponent should be either a StaticMeshComponent, an InstancedStaticMeshComponent or an InstancedActorComponent
UStaticMeshComponent* SMC = Cast< UStaticMeshComponent >( MeshComponent );
UInstancedStaticMeshComponent* ISMC = Cast< UInstancedStaticMeshComponent >( MeshComponent );
UHierarchicalInstancedStaticMeshComponent* HISMC = Cast< UHierarchicalInstancedStaticMeshComponent >( MeshComponent );
UHoudiniInstancedActorComponent* IAC = Cast< UHoudiniInstancedActorComponent >( MeshComponent );
UHoudiniMeshSplitInstancerComponent* MSPIC = Cast<UHoudiniMeshSplitInstancerComponent>(MeshComponent);
UStaticMesh* SM = Cast< UStaticMesh >( MeshComponent );
UBodySetup* BS = Cast< UBodySetup >( MeshComponent );
if ( !SMC && !HISMC && !ISMC && !IAC && !MSPIC && !SM )
return;
UClass* MeshClass = IAC ? IAC->StaticClass()
: HISMC ? HISMC->StaticClass()
: ISMC ? ISMC->StaticClass()
: MSPIC ? MSPIC->StaticClass()
: SMC ? SMC->StaticClass()
: SM ? SM->StaticClass()
: BS->StaticClass();
// Trying to find the UProps in the object
for ( int32 nAttributeIdx = 0; nAttributeIdx < nUPropsCount; nAttributeIdx++ )
{
UGenericAttribute CurrentPropAttribute = UPropertiesToModify[ nAttributeIdx ];
FString CurrentUPropertyName = CurrentPropAttribute.AttributeName;
if ( CurrentUPropertyName.IsEmpty() )
continue;
// We have to iterate manually on the properties, in order to handle structProperties correctly
void* StructContainer = nullptr;
UProperty* FoundProperty = nullptr;
bool bPropertyHasBeenFound = false;
for ( TFieldIterator< UProperty > PropIt( MeshClass ); PropIt; ++PropIt )
{
UProperty* CurrentProperty = *PropIt;
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( CurrentUPropertyName ) || DisplayName.Contains( CurrentUPropertyName ) )
{
FoundProperty = CurrentProperty;
// If it's an equality, we dont need to keep searching
if ( ( Name == CurrentUPropertyName ) || ( DisplayName == CurrentUPropertyName ) )
{
bPropertyHasBeenFound = true;
break;
}
}
// StructProperty need to be a nested struct
if ( FStructProperty* StructProperty = CastField< UStructProperty >( CurrentProperty ) )
{
// 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;
DisplayName = It->GetDisplayNameText().ToString().Replace( TEXT(" "), TEXT("") );
Name = It->GetName();
// If the property name contains the uprop attribute name, we have a candidate
if ( Name.Contains( CurrentUPropertyName ) || DisplayName.Contains( CurrentUPropertyName ) )
{
// 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 >( MeshComponent, 0 );
// If it's an equality, we dont need to keep searching
if ( ( Name == CurrentUPropertyName ) || ( DisplayName == CurrentUPropertyName ) )
{
bPropertyHasBeenFound = true;
break;
}
}
}
}
if ( bPropertyHasBeenFound )
break;
}
if ( !FoundProperty )
{
// Property not found
HOUDINI_LOG_MESSAGE( TEXT( "Could not find UProperty: %s"), *( CurrentPropAttribute.AttributeName ) );
continue;
}
// The found property's class (TODO Remove me! for debug only)
UClass* PropertyClass = FoundProperty->GetClass();
UProperty* InnerProperty = FoundProperty;
int32 NumberOfProperties = 1;
if ( FArrayProperty* ArrayProperty = CastField< FArrayProperty >( FoundProperty ) )
{
InnerProperty = ArrayProperty->Inner;
NumberOfProperties = ArrayProperty->ArrayDim;
// Do we need to add values to the array?
FScriptArrayHelper_InContainer ArrayHelper( ArrayProperty, CurrentPropAttribute.GetData() );
//ArrayHelper.ExpandForIndex( CurrentPropAttribute.AttributeTupleSize - 1 );
if ( CurrentPropAttribute.AttributeTupleSize > NumberOfProperties )
{
ArrayHelper.Resize( CurrentPropAttribute.AttributeTupleSize );
NumberOfProperties = CurrentPropAttribute.AttributeTupleSize;
}
}
for( int32 nPropIdx = 0; nPropIdx < NumberOfProperties; nPropIdx++ )
{
if ( FFloatProperty* FloatProperty = CastField< FFloatProperty >( InnerProperty ) )
{
// FLOAT PROPERTY
if ( CurrentPropAttribute.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING )
{
FString Value = CurrentPropAttribute.GetStringValue( nPropIdx );
FloatProperty->SetNumericPropertyValueFromString(
StructContainer ?
InnerProperty->ContainerPtrToValuePtr< FString >( ( uint8 * )StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx ),
*Value );
}
else
{
double Value = CurrentPropAttribute.GetDoubleValue( nPropIdx );
FloatProperty->SetFloatingPointPropertyValue(
StructContainer ?
InnerProperty->ContainerPtrToValuePtr< float >( ( uint8 * )StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< float >( MeshComponent, nPropIdx ),
Value );
}
}
else if ( FIntProperty* IntProperty = CastField< FIntProperty >( InnerProperty ) )
{
// INT PROPERTY
if ( CurrentPropAttribute.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING )
{
FString Value = CurrentPropAttribute.GetStringValue( nPropIdx );
IntProperty->SetNumericPropertyValueFromString(
StructContainer ?
InnerProperty->ContainerPtrToValuePtr< FString >( ( uint8 * )StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx ),
*Value );
}
else
{
int64 Value = CurrentPropAttribute.GetIntValue( nPropIdx );
IntProperty->SetIntPropertyValue(
StructContainer ?
InnerProperty->ContainerPtrToValuePtr< int64 >( ( uint8 * )StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< int64 >( MeshComponent, nPropIdx ),
Value );
}
}
else if ( FBoolProperty* BoolProperty = CastField< FBoolProperty >( InnerProperty ) )
{
// BOOL PROPERTY
bool Value = CurrentPropAttribute.GetBoolValue( nPropIdx );
BoolProperty->SetPropertyValue(
StructContainer ?
InnerProperty->ContainerPtrToValuePtr< bool >( ( uint8* )StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< bool >( MeshComponent, nPropIdx ),
Value );
}
else if ( FStrProperty* StringProperty = CastField< FStrProperty >( InnerProperty ) )
{
// STRING PROPERTY
FString Value = CurrentPropAttribute.GetStringValue( nPropIdx );
StringProperty->SetPropertyValue(
StructContainer ?
InnerProperty->ContainerPtrToValuePtr< FString >( ( uint8* )StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx ),
Value );
}
else if ( FNumericProperty *NumericProperty = CastField< FNumericProperty >( InnerProperty ) )
{
// NUMERIC PROPERTY
if ( CurrentPropAttribute.AttributeType == HAPI_StorageType::HAPI_STORAGETYPE_STRING )
{
FString Value = CurrentPropAttribute.GetStringValue( nPropIdx );
NumericProperty->SetNumericPropertyValueFromString(
StructContainer ?
InnerProperty->ContainerPtrToValuePtr< FString >( ( uint8 * )StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< FString >( MeshComponent, nPropIdx ),
*Value );
}
else if ( NumericProperty->IsInteger() )
{
int64 Value = CurrentPropAttribute.GetIntValue( nPropIdx );
NumericProperty->SetIntPropertyValue(
StructContainer ?
InnerProperty->ContainerPtrToValuePtr< int64 >( (uint8*)StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< int64 >( MeshComponent, nPropIdx ),
(int64)Value );
}
else if ( NumericProperty->IsFloatingPoint() )
{
double Value = CurrentPropAttribute.GetDoubleValue( nPropIdx );
NumericProperty->SetFloatingPointPropertyValue(
StructContainer ?
InnerProperty->ContainerPtrToValuePtr< float >( ( uint8 * )StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< float >( MeshComponent, nPropIdx ),
Value );
}
else
{
// Numeric Property was found, but is of an unsupported type
HOUDINI_LOG_MESSAGE( TEXT( "Unsupported Numeric UProperty" ) );
}
}
else if ( FNameProperty* NameProperty = CastField< FNameProperty >( InnerProperty ) )
{
// NAME PROPERTY
FString StringValue = CurrentPropAttribute.GetStringValue( nPropIdx );
FName Value = FName( *StringValue );
NameProperty->SetPropertyValue(
StructContainer ?
InnerProperty->ContainerPtrToValuePtr< FName >( ( uint8* )StructContainer, nPropIdx )
: InnerProperty->ContainerPtrToValuePtr< FName >( MeshComponent, nPropIdx ),
Value );
}
else if ( FStructProperty* StructProperty = CastField< FStructProperty >( InnerProperty ) )
{
// STRUCT PROPERTY
const FName PropertyName = StructProperty->Struct->GetFName();
if ( PropertyName == NAME_Vector )
{
FVector& PropertyValue = StructContainer ?
*( InnerProperty->ContainerPtrToValuePtr< FVector >( (uint8 *) StructContainer, nPropIdx ) )
: *( InnerProperty->ContainerPtrToValuePtr< FVector >( MeshComponent, nPropIdx ) );
// Found a vector property, fill it with the 3 tuple values
PropertyValue.X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 0 );
PropertyValue.Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 1 );
PropertyValue.Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 2 );
}
else if ( PropertyName == NAME_Transform )
{
FTransform& PropertyValue = StructContainer ?
*( InnerProperty->ContainerPtrToValuePtr< FTransform >( (uint8 *) StructContainer, nPropIdx ) )
: *( InnerProperty->ContainerPtrToValuePtr< FTransform >( MeshComponent, nPropIdx ) );
// Found a transform property fill it with the attribute tuple values
FVector Translation;
Translation.X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 0 );
Translation.Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 1 );
Translation.Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 2 );
FQuat Rotation;
Rotation.W = CurrentPropAttribute.GetDoubleValue( nPropIdx + 3 );
Rotation.X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 4 );
Rotation.Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 5 );
Rotation.Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 6 );
FVector Scale;
Scale.X = CurrentPropAttribute.GetDoubleValue( nPropIdx + 7 );
Scale.Y = CurrentPropAttribute.GetDoubleValue( nPropIdx + 8 );
Scale.Z = CurrentPropAttribute.GetDoubleValue( nPropIdx + 9 );
PropertyValue.SetTranslation( Translation );
PropertyValue.SetRotation( Rotation );
PropertyValue.SetScale3D( Scale );
}
else if ( PropertyName == NAME_Color )
{
FColor& PropertyValue = StructContainer ?
*(InnerProperty->ContainerPtrToValuePtr< FColor >( (uint8 *)StructContainer, nPropIdx ) )
: *(InnerProperty->ContainerPtrToValuePtr< FColor >( MeshComponent, nPropIdx ) );
PropertyValue.R = (int8)CurrentPropAttribute.GetIntValue( nPropIdx );
PropertyValue.G = (int8)CurrentPropAttribute.GetIntValue( nPropIdx + 1 );
PropertyValue.B = (int8)CurrentPropAttribute.GetIntValue( nPropIdx + 2 );
if ( CurrentPropAttribute.AttributeTupleSize == 4 )
PropertyValue.A = (int8)CurrentPropAttribute.GetIntValue( nPropIdx + 3 );
}
else if ( PropertyName == NAME_LinearColor )
{
FLinearColor& PropertyValue = StructContainer ?
*(InnerProperty->ContainerPtrToValuePtr< FLinearColor >( (uint8 *)StructContainer, nPropIdx ) )
: *(InnerProperty->ContainerPtrToValuePtr< FLinearColor >( MeshComponent, nPropIdx ) );
PropertyValue.R = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx );
PropertyValue.G = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx + 1 );
PropertyValue.B = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx + 2 );
if ( CurrentPropAttribute.AttributeTupleSize == 4 )
PropertyValue.A = (float)CurrentPropAttribute.GetDoubleValue( nPropIdx + 3 );
}
}
else
{
// Property was found, but is of an unsupported type
HOUDINI_LOG_MESSAGE( TEXT("Unsupported UProperty Class: %s found for uproperty %s" ), *PropertyClass->GetName(), *CurrentPropAttribute.AttributeName );
continue;
}
// Some UProperties might need some additional tweaks...
if ( CurrentUPropertyName == "CollisionProfileName" )
{
FString StringValue = CurrentPropAttribute.GetStringValue( nPropIdx );
FName Value = FName( *StringValue );
if ( SMC )
SMC->SetCollisionProfileName( Value );
else if ( ISMC )
ISMC->SetCollisionProfileName( Value );
}
}
}
#endif
}
*/
int32
FHoudiniEngineUtils::HapiGetAttributeOfType(
const HAPI_NodeId& AssetId,
const HAPI_NodeId& ObjectId,
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<HAPI_StringHandle> AttribNameSHArray;
AttribNameSHArray.SetNum( nAttribCount );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeNames(
FHoudiniEngine::Get().GetSession(),
GeoId, PartInfo.id, 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 HoudiniEngineString( AttribNameSHArray[ Idx ] );
HoudiniEngineString.ToFString( HapiString );
// ... then the attribute info
HAPI_AttributeInfo AttrInfo;
FHoudiniApi::AttributeInfo_Init(&AttrInfo);
//FMemory::Memzero< HAPI_AttributeInfo >( AttrInfo );
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo(
FHoudiniEngine::Get().GetSession(),
GeoId, PartInfo.id, 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;
}
void
FHoudiniEngineUtils::GetAllUVAttributesInfoAndTexCoords(
const HAPI_NodeId& AssetId, const HAPI_NodeId& ObjectId,
const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId,
TArray< HAPI_AttributeInfo >& AttribInfoUVs,
TArray< TArray< float > >& TextureCoordinates )
{
// 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(AssetId, ObjectId, GeoId, 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 );
FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
AssetId, ObjectId, GeoId, PartId, TCHAR_TO_ANSI(*UVAttributeName),
AttribInfoUVs[ TexCoordIdx ], TextureCoordinates[ 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< HAPI_AttributeInfo > FoundAttributeInfos;
TArray< FString > FoundAttributeNames;
for ( int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx )
{
HapiGetAttributeOfType( AssetId, ObjectId, GeoId, PartId,
( HAPI_AttributeOwner ) AttrIdx, HAPI_ATTRIBUTE_TYPE_TEXTURE,
FoundAttributeInfos, FoundAttributeNames );
}
if ( FoundAttributeInfos.Num() <= 0 )
return;
// 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 < AttribInfoUVs.Num(); AvailableIdx++ )
{
if ( !AttribInfoUVs[ 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 >= AttribInfoUVs.Num() ) )
{
HOUDINI_LOG_WARNING( TEXT( "Too many UV sets found. Unreal only supports %d , skipping the remaining uv sets." ), (int32)MAX_STATIC_TEXCOORDS );
break;
}
/*
// We need to add a new attribute info and a new float array for the texcoords
if ( AvailableIdx > AttribInfoUVs.Num() )
{
HAPI_AttributeInfo NewAttrInfo;
FMemory::MemZero<HAPI_AttributeInfo>( NewAttrInfo );
AttribInfoUVs.Add( NewAttrInfo );
TArray< float > NewTexCoords;
TextureCoordinates.Add( NewTexCoords );
}
*/
// Force the tuple size to 2 ?
CurrentAttrInfo.tupleSize = 2;
// Add the attribute infos we found
AttribInfoUVs[ AvailableIdx ] = CurrentAttrInfo;
// Allocate sufficient buffer for the attribute's data.
TextureCoordinates[ AvailableIdx ].SetNumUninitialized( CurrentAttrInfo.count * CurrentAttrInfo.tupleSize );
// Get the texture coordinates
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData(
FHoudiniEngine::Get().GetSession(), GeoId, PartId,
TCHAR_TO_UTF8( *( FoundAttributeNames[ attrIdx ] ) ),
&AttribInfoUVs[ AvailableIdx ], -1,
&TextureCoordinates[ AvailableIdx ][0], 0, CurrentAttrInfo.count ) )
{
// Something went wrong when trying to access the uv values, invalidate this set
AttribInfoUVs[ AvailableIdx ].exists = false;
}
}
}
bool
FHoudiniEngineUtils::AddConvexCollisionToAggregate(
const TArray<float>& Positions, const TArray<int32>& SplitGroupVertexList,
const bool& MultiHullDecomp, FKAggregateGeom& AggregateCollisionGeo )
{
#if WITH_EDITOR
// Get runtime settings.
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
check( HoudiniRuntimeSettings );
float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION;
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
if ( HoudiniRuntimeSettings )
{
GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor;
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
}
// We're only interested in the unique vertices
TArray<int32> UniqueVertexIndexes;
for ( int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++ )
{
int32 Index = SplitGroupVertexList[ VertexIdx ];
if ( Index < 0 || ( Index >= Positions.Num() ) )
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 ( !Positions.IsValidIndex( VertexIndex * 3 + 2 ) )
continue;
VertexArray[ Idx ].X = Positions[ VertexIndex * 3 + 0 ] * GeneratedGeometryScaleFactor;
if ( ImportAxis == HRSAI_Unreal )
{
VertexArray[ Idx ].Y = Positions[ VertexIndex * 3 + 2 ] * GeneratedGeometryScaleFactor;
VertexArray[ Idx ].Z = Positions[ VertexIndex * 3 + 1 ] * GeneratedGeometryScaleFactor;
}
else
{
VertexArray[ Idx ].Y = Positions[ VertexIndex * 3 + 1 ] * GeneratedGeometryScaleFactor;
VertexArray[ Idx ].Z = Positions[ VertexIndex * 3 + 2 ] * GeneratedGeometryScaleFactor;
}
}
if ( MultiHullDecomp && ( 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<uint32> Indices;
for ( int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++ )
{
int32 Index = SplitGroupVertexList[ VertexIdx ];
if ( Index < 0 || ( Index >= Positions.Num() ) )
continue;
Indices.Add( Index );
}
// But we need all the positions as vertex
TArray< FVector > Vertices;
Vertices.SetNum(Positions.Num() / 3);
for ( int32 Idx = 0; Idx < Vertices.Num(); Idx++ )
{
Vertices[ Idx ].X = Positions[ Idx * 3 + 0 ] * GeneratedGeometryScaleFactor;
if ( ImportAxis == HRSAI_Unreal )
{
Vertices[ Idx ].Y = Positions[ Idx * 3 + 2 ] * GeneratedGeometryScaleFactor;
Vertices[ Idx ].Z = Positions[ Idx * 3 + 1 ] * GeneratedGeometryScaleFactor;
}
else
{
Vertices[ Idx ].Y = Positions[ Idx * 3 + 1 ] * GeneratedGeometryScaleFactor;
Vertices[ Idx ].Z = Positions[ Idx * 3 + 2 ] * GeneratedGeometryScaleFactor;
}
}
// We are using Unreal's DecomposeMeshToHulls() so we have to create a fake BodySetup
UBodySetup* BodySetup = NewObject<UBodySetup>();
// Run actual util to do the work (if we have some valid input)
DecomposeMeshToHulls( BodySetup, Vertices, Indices, 8, 16 );
// 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++ )
AggregateCollisionGeo.ConvexElems.Add( BodySetup->AggGeom.ConvexElems[ n ] );
return true;
}
}
// Creating a single Convex collision
FKConvexElem ConvexCollision;
ConvexCollision.VertexData = VertexArray;
ConvexCollision.UpdateElemBox();
AggregateCollisionGeo.ConvexElems.Add( ConvexCollision );
#endif
return true;
}
bool
FHoudiniEngineUtils::AddSimpleCollision(
const FString& SplitGroupName, UStaticMesh* StaticMesh,
FHoudiniGeoPartObject& HoudiniGeoPartObject, FKAggregateGeom& AggregateCollisionGeo, bool& bSimpleCollisionAddedToAggregate )
{
if ( !StaticMesh || StaticMesh->IsPendingKill() )
return false;
#if WITH_EDITOR
int32 PrimIndex = INDEX_NONE;
if ( SplitGroupName.Contains( "Box" ) )
{
PrimIndex = GenerateBoxAsSimpleCollision( StaticMesh );
if ( !HoudiniGeoPartObject.bIsRenderCollidable )
{
// If this part is not renderCollidable, we want to extract the Box collider
// and add it to the AggregateCollisionGeo to avoid creating extra meshes
UBodySetup* bs = StaticMesh->BodySetup;
if (bs && !bs->IsPendingKill() && bs->AggGeom.BoxElems.IsValidIndex( PrimIndex ) )
{
AggregateCollisionGeo.BoxElems.Add( bs->AggGeom.BoxElems[ PrimIndex ] );
bSimpleCollisionAddedToAggregate = true;
}
}
}
else if ( SplitGroupName.Contains( "Sphere" ) )
{
PrimIndex = GenerateSphereAsSimpleCollision( StaticMesh );
if (!HoudiniGeoPartObject.bIsRenderCollidable)
{
// If this part is not renderCollidable, we want to extract the Sphere collider
// and add it to the AggregateCollisionGeo to avoid creating extra meshes
UBodySetup* bs = StaticMesh->BodySetup;
if ( bs && !bs->IsPendingKill() && bs->AggGeom.SphereElems.IsValidIndex( PrimIndex ) )
{
AggregateCollisionGeo.SphereElems.Add(bs->AggGeom.SphereElems[PrimIndex]);
bSimpleCollisionAddedToAggregate = true;
}
}
}
else if ( SplitGroupName.Contains( "Capsule" ) )
{
PrimIndex = GenerateSphylAsSimpleCollision( StaticMesh );
if (!HoudiniGeoPartObject.bIsRenderCollidable)
{
// If this part is not renderCollidable, we want to extract the Capsule collider
// and add it to the AggregateCollisionGeo to avoid creating extra meshes
UBodySetup* bs = StaticMesh->BodySetup;
if ( bs && !bs->IsPendingKill() && bs->AggGeom.SphylElems.IsValidIndex( PrimIndex ) )
{
AggregateCollisionGeo.SphylElems.Add(bs->AggGeom.SphylElems[PrimIndex]);
bSimpleCollisionAddedToAggregate = true;
}
}
}
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<FVector> DirArray;
for ( uint32 DirectionIndex = 0; DirectionIndex < NumDirections; DirectionIndex++ )
{
DirArray.Add( Directions[DirectionIndex] );
}
PrimIndex = GenerateKDopAsSimpleCollision( StaticMesh, DirArray );
if (!HoudiniGeoPartObject.bIsRenderCollidable)
{
// If this part is not renderCollidable, we want to extract the KDOP collider
// and add it to the AggregateCollisionGeo to avoid creating extra meshes
UBodySetup* bs = StaticMesh->BodySetup;
if (bs && !bs->IsPendingKill() && bs->AggGeom.ConvexElems.IsValidIndex( PrimIndex ) )
{
AggregateCollisionGeo.ConvexElems.Add(bs->AggGeom.ConvexElems[PrimIndex]);
bSimpleCollisionAddedToAggregate = true;
}
}
}
// We somehow couldnt create the simple collider
if ( PrimIndex == INDEX_NONE )
{
HoudiniGeoPartObject.bIsSimpleCollisionGeo = false;
HoudiniGeoPartObject.bIsCollidable = false;
HoudiniGeoPartObject.bIsRenderCollidable = false;
return false;
}
#endif
return true;
}
void
FHoudiniEngineUtils::CreateSlateNotification( const FString& NotificationString )
{
#if WITH_EDITOR
// Check whether we want to display Slate notifications.
bool bDisplaySlateCookingNotifications = true;
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
if (HoudiniRuntimeSettings)
bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications;
if (!bDisplaySlateCookingNotifications)
return;
static float NotificationFadeOutDuration = 2.0f;
static float NotificationExpireDuration = 2.0f;
FText NotificationText = FText::FromString(NotificationString);
FNotificationInfo Info(NotificationText);
Info.bFireAndForget = true;
Info.FadeOutDuration = NotificationFadeOutDuration;
Info.ExpireDuration = NotificationExpireDuration;
TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniLogoBrush();
if (HoudiniBrush.IsValid())
Info.Image = HoudiniBrush.Get();
FSlateNotificationManager::Get().AddNotification(Info);
#endif
return;
}
HAPI_NodeId
FHoudiniEngineUtils::HapiGetParentNodeId( const HAPI_NodeId& NodeId )
{
HAPI_NodeId ParentId = -1;
if ( NodeId >= 0 )
{
HAPI_NodeInfo NodeInfo;
FHoudiniApi::NodeInfo_Init(&NodeInfo);
if ( HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo( FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo ) )
ParentId = NodeInfo.parentId;
}
return ParentId;
}
bool
FHoudiniEngineUtils::CreateGroupOrAttributeFromTags( const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray<FName>& Tags, const bool& bCreateAttributes )
{
if ( Tags.Num() <= 0 )
return false;
HAPI_Result Result = HAPI_RESULT_FAILURE;
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);
if ( bCreateAttributes )
{
// Create a primitive attribute for the tag
HAPI_AttributeInfo AttributeInfo;
FHoudiniApi::AttributeInfo_Init(&AttributeInfo);
//FMemory::Memzero< HAPI_AttributeInfo >( 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("unreal_tag_") + 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<HAPI_StringHandle> TagSHArray;
TArray<const char*> TagStr;
TagStr.Add( FHoudiniEngineUtils::ExtractRawName(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;
}
else
{
// Create a primitive group for this tag
Result = FHoudiniApi::AddGroup( FHoudiniEngine::Get().GetSession(),
NodeId, 0, HAPI_GROUPTYPE_PRIM,
FHoudiniEngineUtils::ExtractRawName( TagString ) );
if ( Result != HAPI_RESULT_SUCCESS )
continue;
// Set GroupMembership
TArray<int> GroupArray;
GroupArray.Init( 1, PartInfo.faceCount );
Result = FHoudiniApi::SetGroupMembership( FHoudiniEngine::Get().GetSession(),
NodeId, PartId, HAPI_GROUPTYPE_PRIM,
FHoudiniEngineUtils::ExtractRawName( TagString ),
GroupArray.GetData(), 0, PartInfo.faceCount );
if ( HAPI_RESULT_SUCCESS == Result )
NeedToCommitGeo = true;
}
}
//if (NeedToCommitGeo)
// Result = FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), NodeId);
return NeedToCommitGeo;
}
bool
FHoudiniEngineUtils::GetUnrealTagAttributes(const FHoudiniGeoPartObject& HoudiniGeoPartObject, TArray<FName>& OutTags)
{
FString TagAttribBase = TEXT("unreal_tag_");
bool bAttributeFound = true;
int32 TagIdx = 0;
while (bAttributeFound)
{
FString CurrentTagAttr = TagAttribBase + FString::FromInt(TagIdx++);
bAttributeFound = HapiCheckAttributeExists(HoudiniGeoPartObject, 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<FString> StringData;
if ( FHoudiniEngineUtils::HapiGetAttributeDataAsString(
HoudiniGeoPartObject, TCHAR_TO_UTF8(*CurrentTagAttr), AttributeInfo, StringData, 1, HAPI_ATTROWNER_PRIM) )
{
TagValue = StringData[0];
}
}
FName NameTag = *TagValue;
OutTags.Add(NameTag);
}
return true;
}
FHoudiniCookParams::FHoudiniCookParams( class UHoudiniAsset* InHoudiniAsset )
: HoudiniAsset( InHoudiniAsset )
{
PackageGUID = FGuid::NewGuid();
TempCookFolder = LOCTEXT( "Temp", "/Game/HoudiniEngine/Temp" );
}
#undef LOCTEXT_NAMESPACE