Files

4335 lines
176 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.
*
* Produced by:
* Mykola Konyk
* Side Effects Software Inc
* 123 Front Street West, Suite 1401
* Toronto, Ontario
* Canada M5J 2M2
* 416-504-9876
*
*/
#include "HoudiniLandscapeUtils.h"
#include "HoudiniApi.h"
#include "HoudiniEngineRuntimePrivatePCH.h"
#include "HoudiniRuntimeSettings.h"
#include "HoudiniEngineUtils.h"
#include "HoudiniEngine.h"
#include "HoudiniEngineString.h"
#include "HoudiniCookHandler.h"
#include "HoudiniAsset.h"
#include "HoudiniAssetActor.h"
#include "HoudiniAssetComponent.h"
#include "LandscapeInfo.h"
#include "LandscapeComponent.h"
#include "LandscapeEdit.h"
#include "LandscapeLayerInfoObject.h"
#include "LandscapeStreamingProxy.h"
#include "LightMap.h"
#include "Engine/MapBuildDataRegistry.h"
#if WITH_EDITOR
#include "FileHelpers.h"
#include "EngineUtils.h"
#include "LandscapeEditorModule.h"
#include "LandscapeFileFormatInterface.h"
#endif
void
FHoudiniLandscapeUtils::GetHeightfieldsInArray(
const TArray< FHoudiniGeoPartObject >& InArray,
TArray< const FHoudiniGeoPartObject* >& FoundHeightfields )
{
FoundHeightfields.Empty();
// First, we need to extract proper height data from FoundVolumes
for ( TArray< FHoudiniGeoPartObject >::TConstIterator Iter( InArray ); Iter; ++Iter )
{
const FHoudiniGeoPartObject & HoudiniGeoPartObject = *Iter;
if ( !HoudiniGeoPartObject.IsVolume() )
continue;
// Retrieve node id from geo part.
//HAPI_NodeId NodeId = HoudiniGeoPartObject.HapiGeoGetNodeId( AssetId );
HAPI_NodeId NodeId = HoudiniGeoPartObject.HapiGeoGetNodeId();
if ( NodeId == -1 )
continue;
// Retrieve the VolumeInfo
HAPI_VolumeInfo CurrentVolumeInfo;
FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo);
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo(
FHoudiniEngine::Get().GetSession(),
NodeId, HoudiniGeoPartObject.PartId,
&CurrentVolumeInfo ) )
continue;
// We're only interested in heightfields
FString CurrentVolumeName;
FHoudiniEngineString( CurrentVolumeInfo.nameSH ).ToFString( CurrentVolumeName );
if ( !CurrentVolumeName.Contains( "height" ) )
continue;
// We're only handling single values for now
if ( CurrentVolumeInfo.tupleSize != 1 )
continue;
// Terrains always have a ZSize of 1.
if ( CurrentVolumeInfo.zLength != 1 )
continue;
// Values should be float
if ( CurrentVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT )
continue;
FoundHeightfields.Add( &HoudiniGeoPartObject );
}
}
void
FHoudiniLandscapeUtils::GetHeightfieldsLayersInArray(
const TArray< FHoudiniGeoPartObject >& InArray,
const FHoudiniGeoPartObject& Heightfield,
TArray< const FHoudiniGeoPartObject* >& FoundLayers )
{
FoundLayers.Empty();
// We need the parent heightfield's node ID and tile attribute
HAPI_NodeId HeightFieldNodeId = Heightfield.HapiGeoGetNodeId();
// We need the tile attribute if the height has it
bool bParentHeightfieldHasTile = false;
int32 HeightFieldTile = -1;
{
HAPI_AttributeInfo AttribInfoTile;
FHoudiniApi::AttributeInfo_Init(&AttribInfoTile);
TArray< int32 > TileValues;
FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(
Heightfield, "tile",
AttribInfoTile, TileValues );
if ( AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0 )
{
HeightFieldTile = TileValues[ 0 ];
bParentHeightfieldHasTile = true;
}
}
// Look for all the layers/masks corresponding to the current heightfield
for ( TArray< FHoudiniGeoPartObject >::TConstIterator IterLayers( InArray ); IterLayers; ++IterLayers )
{
const FHoudiniGeoPartObject & HoudiniGeoPartObject = *IterLayers;
if ( !HoudiniGeoPartObject.IsVolume() )
continue;
// Retrieve node id from geo part.
HAPI_NodeId NodeId = HoudiniGeoPartObject.HapiGeoGetNodeId();
if ( NodeId == -1 )
continue;
// It needs to be from the same node as the parent heightfield...
if ( NodeId != HeightFieldNodeId )
continue;
// If the parent had a tile info, retrieve the tile attribute for the current layer
if ( bParentHeightfieldHasTile )
{
int32 CurrentTile = -1;
HAPI_AttributeInfo AttribInfoTile;
FHoudiniApi::AttributeInfo_Init(&AttribInfoTile);
TArray<int32> TileValues;
FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(
HoudiniGeoPartObject, "tile",
AttribInfoTile, TileValues );
if ( AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0 )
{
CurrentTile = TileValues[ 0 ];
}
// Does this layer come from the same tile as the height?
if ( ( CurrentTile != HeightFieldTile ) || ( CurrentTile == -1 ) )
continue;
}
// Retrieve the VolumeInfo
HAPI_VolumeInfo CurrentVolumeInfo;
FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo);
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo(
FHoudiniEngine::Get().GetSession(),
NodeId, HoudiniGeoPartObject.PartId,
&CurrentVolumeInfo ) )
continue;
// We're interesting in anything but height data
FString CurrentVolumeName;
FHoudiniEngineString( CurrentVolumeInfo.nameSH ).ToFString( CurrentVolumeName );
if ( CurrentVolumeName.Contains( "height" ) )
continue;
// We're only handling single values for now
if ( CurrentVolumeInfo.tupleSize != 1 )
continue;
// Terrains always have a ZSize of 1.
if ( CurrentVolumeInfo.zLength != 1 )
continue;
// Values should be float
if ( CurrentVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT )
continue;
FoundLayers.Add( &HoudiniGeoPartObject );
}
}
void
FHoudiniLandscapeUtils::CalcHeightfieldsArrayGlobalZMinZMax(
const TArray< FHoudiniGeoPartObject > & InHeightfieldArray,
TMap<FString, float>& GlobalMinimums,
TMap<FString, float>& GlobalMaximums )
{
GlobalMinimums.Empty();
GlobalMaximums.Empty();
for (TArray< FHoudiniGeoPartObject >::TConstIterator CurrentHeightfield(InHeightfieldArray); CurrentHeightfield; ++CurrentHeightfield)
{
// Get the current Heightfield GeoPartObject
if (!CurrentHeightfield)
continue;
if (!CurrentHeightfield->IsVolume())
continue;
// Retrieve node id from geo part.
HAPI_NodeId NodeId = CurrentHeightfield->HapiGeoGetNodeId();
if (NodeId == -1)
continue;
// Retrieve the VolumeInfo
HAPI_VolumeInfo CurrentVolumeInfo;
FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo);
if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo(
FHoudiniEngine::Get().GetSession(),
NodeId, CurrentHeightfield->PartId,
&CurrentVolumeInfo))
continue;
// Unreal's Z values are Y in Houdini
float ymin, ymax;
if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(),
NodeId, CurrentHeightfield->PartId,
nullptr, &ymin, nullptr,
nullptr, &ymax, nullptr,
nullptr, nullptr, nullptr))
continue;
// Retrieve the volume name.
FString VolumeName;
FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH);
HoudiniEngineStringPartName.ToFString(VolumeName);
// Read the global min value for this volume
if ( !GlobalMinimums.Contains(VolumeName) )
{
GlobalMinimums.Add(VolumeName, ymin);
}
else
{
// Update the min if necessary
if ( ymin < GlobalMinimums[VolumeName] )
GlobalMinimums[VolumeName] = ymin;
}
// Read the global max value for this volume
float fCurrentVolumeGlobalMax = -MAX_FLT;
if (!GlobalMaximums.Contains(VolumeName))
{
GlobalMaximums.Add(VolumeName, ymax);
}
else
{
// Update the max if necessary
if (ymax > GlobalMaximums[VolumeName])
GlobalMaximums[VolumeName] = ymax;
}
}
}
void
FHoudiniLandscapeUtils::CalcHeightfieldsArrayGlobalZMinZMax(
const TArray< const FHoudiniGeoPartObject* > & InHeightfieldArray,
float& fGlobalMin, float& fGlobalMax )
{
// Initialize the global values
fGlobalMin = MAX_FLT;
fGlobalMax = -MAX_FLT;
for ( TArray< const FHoudiniGeoPartObject* >::TConstIterator IterHeighfields( InHeightfieldArray ); IterHeighfields; ++IterHeighfields )
{
// Get the current Heightfield GeoPartObject
const FHoudiniGeoPartObject* CurrentHeightfield = *IterHeighfields;
if ( !CurrentHeightfield )
continue;
if ( !CurrentHeightfield->IsVolume() )
continue;
// Retrieve node id from geo part.
HAPI_NodeId NodeId = CurrentHeightfield->HapiGeoGetNodeId();
if ( NodeId == -1 )
continue;
// Retrieve the VolumeInfo
HAPI_VolumeInfo CurrentVolumeInfo;
FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo);
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo(
FHoudiniEngine::Get().GetSession(),
NodeId, CurrentHeightfield->PartId,
&CurrentVolumeInfo ) )
continue;
// Unreal's Z values are Y in Houdini
float ymin, ymax;
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds( FHoudiniEngine::Get().GetSession(),
NodeId, CurrentHeightfield->PartId,
nullptr, &ymin, nullptr,
nullptr, &ymax, nullptr,
nullptr, nullptr, nullptr ) )
continue;
if ( ymin < fGlobalMin )
fGlobalMin = ymin;
if ( ymax > fGlobalMax )
fGlobalMax = ymax;
}
// Set Min/Max to zero if we couldn't set the values properly
if ( fGlobalMin > fGlobalMax )
{
fGlobalMin = 0.0f;
fGlobalMax = 0.0f;
}
}
bool FHoudiniLandscapeUtils::GetHeightfieldData(
const FHoudiniGeoPartObject& Heightfield,
TArray<float>& FloatValues,
HAPI_VolumeInfo& VolumeInfo,
float& FloatMin, float& FloatMax )
{
FloatValues.Empty();
FloatMin = 0.0f;
FloatMax = 0.0f;
if ( !Heightfield.IsVolume() )
return false;
// Retrieve node id from geo part.
HAPI_NodeId NodeId = Heightfield.HapiGeoGetNodeId();
if ( NodeId == -1 )
return false;
// Retrieve the VolumeInfo
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetVolumeInfo(
FHoudiniEngine::Get().GetSession(),
NodeId, Heightfield.PartId,
&VolumeInfo ), false );
// We're only handling single values for now
if ( VolumeInfo.tupleSize != 1 )
return false;
// Terrains always have a ZSize of 1.
if ( VolumeInfo.zLength != 1 )
return false;
// Values must be float
if ( VolumeInfo.storage != HAPI_STORAGETYPE_FLOAT )
return false;
if ( ( VolumeInfo.xLength < 2 ) || ( VolumeInfo.yLength < 2 ) )
return false;
int32 SizeInPoints = VolumeInfo.xLength * VolumeInfo.yLength;
int32 TotalSize = SizeInPoints * VolumeInfo.tupleSize;
//--------------------------------------------------------------------------------------------------
// 1. Reading and converting the Height values from HAPI
//--------------------------------------------------------------------------------------------------
FloatValues.SetNumUninitialized( TotalSize );
// Get all the heightfield data
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetHeightFieldData(
FHoudiniEngine::Get().GetSession(),
NodeId, Heightfield.PartId,
FloatValues.GetData(),
0, SizeInPoints ), false );
// We will need the min and max value for the conversion to uint16
FloatMin = FloatValues[0];
FloatMax = FloatMin;
for ( int32 n = 0; n < FloatValues.Num(); n++ )
{
if ( FloatValues[ n ] > FloatMax )
FloatMax = FloatValues[ n ];
else if ( FloatValues[ n ] < FloatMin )
FloatMin = FloatValues[ n ];
}
return true;
}
bool
FHoudiniLandscapeUtils::ConvertHeightfieldDataToLandscapeData(
const TArray< float >& HeightfieldFloatValues,
const HAPI_VolumeInfo& HeightfieldVolumeInfo,
const int32& FinalXSize, const int32& FinalYSize,
float FloatMin, float FloatMax,
TArray< uint16 >& IntHeightData,
FTransform& LandscapeTransform,
const bool& NoResize )
{
IntHeightData.Empty();
LandscapeTransform.SetIdentity();
// HF sizes needs an X/Y swap
int32 HoudiniXSize = HeightfieldVolumeInfo.yLength;
int32 HoudiniYSize = HeightfieldVolumeInfo.xLength;
int32 SizeInPoints = HoudiniXSize * HoudiniYSize;
if ( ( HoudiniXSize < 2 ) || ( HoudiniYSize < 2 ) )
return false;
// Test for potential special cases...
// Just print a warning for now
if ( HeightfieldVolumeInfo.minX != 0 )
HOUDINI_LOG_WARNING( TEXT( "Converting Landscape: heightfield's min X is not zero." ) );
if ( HeightfieldVolumeInfo.minY != 0 )
HOUDINI_LOG_WARNING( TEXT( "Converting Landscape: heightfield's min Y is not zero." ) );
//--------------------------------------------------------------------------------------------------
// 1. Convert values to uint16 using doubles to get the maximum precision during the conversion
//--------------------------------------------------------------------------------------------------
// Extract the HF's current transform
FTransform CurrentVolumeTransform;
{
HAPI_Transform HapiTransform = HeightfieldVolumeInfo.transform;
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 *= 100.0f;
Swap(ObjectTranslation[2], ObjectTranslation[1]);
FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[1], HapiTransform.scale[2]);
CurrentVolumeTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D);
}
// The ZRange in Houdini (in m)
double MeterZRange = (double) ( FloatMax - FloatMin );
// The corresponding unreal digit range (as unreal uses uint16, max is 65535)
// We may want to not use the full range in order to be able to sculpt the landscape past the min/max values after.
const double dUINT16_MAX = (double)UINT16_MAX;
double DigitZRange = 49152.0;
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
if ( HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseFullResolution )
DigitZRange = dUINT16_MAX - 1.0;
// If we are not using the full range, we need to center the digit values so the terrain can be edited up and down
double DigitCenterOffset = FMath::FloorToDouble( ( dUINT16_MAX - DigitZRange ) / 2.0 );
// The factor used to convert from Houdini's ZRange to the desired digit range
double ZSpacing = ( MeterZRange != 0.0 ) ? ( DigitZRange / MeterZRange ) : 0.0;
// Changes these values if the user wants to loose a lot of precision
// just to keep the same transform as the landscape input
bool bUseDefaultUE4Scaling = false;
if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling)
bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling;
if ( bUseDefaultUE4Scaling )
{
//Check that our values are compatible with UE4's default scale values
if (FloatMin < -256.0f || FloatMin > 256.0f || FloatMax < -256.0f || FloatMax > 256.0f)
{
// Warn the user that the landscape conversion will have issues
// invite him to change that setting
HOUDINI_LOG_WARNING(
TEXT("The heightfield's min and max height values are too large for being used with the \"Use Default UE4 scaling\" option.\n \
The generated Heightfield will likely be incorrectly converted to landscape unless you disable that option in the project settings and recook the asset."));
}
DigitZRange = dUINT16_MAX - 1.0;
DigitCenterOffset = 0;
// Default unreal landscape scaling is -256m:256m at Scale = 100
// We need to apply the scale back to
FloatMin = -256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f;
FloatMax = 256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f;
MeterZRange = (double)(FloatMax - FloatMin);
ZSpacing = ((double)DigitZRange) / MeterZRange;
}
// Converting the data from Houdini to Unreal
// For correct orientation in unreal, the point matrix has to be transposed.
IntHeightData.SetNumUninitialized( SizeInPoints );
int32 nUnreal = 0;
for (int32 nY = 0; nY < HoudiniYSize; nY++)
{
for (int32 nX = 0; nX < HoudiniXSize; nX++)
{
// Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y
int32 nHoudini = nY + nX * HoudiniYSize;
// Get the double values in [0 - ZRange]
double DoubleValue = (double)HeightfieldFloatValues[nHoudini] - (double)FloatMin;
// Then convert it to [0 - DesiredRange] and center it
DoubleValue = DoubleValue * ZSpacing + DigitCenterOffset;
IntHeightData[nUnreal++] = FMath::RoundToInt(DoubleValue);
}
}
//--------------------------------------------------------------------------------------------------
// 2. Resample / Pad the int data so that if fits unreal size requirements
//--------------------------------------------------------------------------------------------------
// UE has specific size requirements for landscape,
// so we might need to pad/resample the heightfield data
FVector LandscapeResizeFactor = FVector::OneVector;
FVector LandscapePositionOffsetInPixels = FVector::ZeroVector;
if (!NoResize)
{
// Try to resize the data
if ( !FHoudiniLandscapeUtils::ResizeHeightDataForLandscape(
IntHeightData,
HoudiniXSize, HoudiniYSize, FinalXSize, FinalYSize,
LandscapeResizeFactor, LandscapePositionOffsetInPixels))
return false;
}
//--------------------------------------------------------------------------------------------------
// 3. Calculating the proper transform for the landscape to be sized and positionned properly
//--------------------------------------------------------------------------------------------------
// Scale:
// Calculating the equivalent scale to match Houdini's Terrain Size in Unreal
FVector LandscapeScale;
// Unreal has a X/Y resolution of 1m per point while Houdini is dependant on the heighfield's grid spacing
LandscapeScale.X = CurrentVolumeTransform.GetScale3D().X * 2.0f;
LandscapeScale.Y = CurrentVolumeTransform.GetScale3D().Y * 2.0f;
// Calculating the Z Scale so that the Z values in Unreal are the same as in Houdini
// Unreal has a default Z range is 512m for a scale of a 100%
LandscapeScale.Z = (float)( (double)( dUINT16_MAX / DigitZRange ) * MeterZRange / 512.0 );
if ( bUseDefaultUE4Scaling )
LandscapeScale.Z = CurrentVolumeTransform.GetScale3D().Z * 2.0f;
LandscapeScale *= 100.f;
// If the data was resized and not expanded, we need to modify the landscape's scale
LandscapeScale *= LandscapeResizeFactor;
// Don't allow a zero scale, as this results in divide by 0 operations in FMatrix::InverseFast in the landscape component.
if (FMath::IsNearlyZero(LandscapeScale.Z))
LandscapeScale.Z = 1.0f;
// We'll use the position from Houdini, but we will need to offset the Z Position to center the
// values properly as the data has been offset by the conversion to uint16
FVector LandscapePosition = CurrentVolumeTransform.GetLocation();
//LandscapePosition.Z = 0.0f;
// We need to calculate the position offset so that Houdini and Unreal have the same Zero position
// In Unreal, zero has a height value of 32768.
// These values are then divided by 128 internally, and then multiplied by the Landscape's Z scale
// ( DIGIT - 32768 ) / 128 * ZScale = ZOffset
// We need the Digit (Unreal) value of Houdini's zero for the scale calculation
// ( float and int32 are used for this because 0 might be out of the landscape Z range!
// when using the full range, this would cause an overflow for a uint16!! )
float HoudiniZeroValueInDigit = (float)FMath::RoundToInt((0.0 - (double)FloatMin) * ZSpacing + DigitCenterOffset);
float ZOffset = -( HoudiniZeroValueInDigit - 32768.0f ) / 128.0f * LandscapeScale.Z;
LandscapePosition.Z += ZOffset;
// If we have padded the data when resizing the landscape, we need to offset the position because of
// the added values on the topLeft Corner of the Landscape
if ( LandscapePositionOffsetInPixels != FVector::ZeroVector )
{
FVector LandscapeOffset = LandscapePositionOffsetInPixels * LandscapeScale;
LandscapeOffset.Z = 0.0f;
LandscapePosition += LandscapeOffset;
}
// Landscape rotation
//FRotator LandscapeRotation( 0.0, -90.0, 0.0 );
//Landscape->SetActorRelativeRotation( LandscapeRotation );
// We can now set the Landscape position
LandscapeTransform.SetLocation( LandscapePosition );
LandscapeTransform.SetScale3D( LandscapeScale );
return true;
}
bool FHoudiniLandscapeUtils::ConvertHeightfieldLayerToLandscapeLayer(
const TArray<float>& FloatLayerData,
const int32& HoudiniXSize, const int32& HoudiniYSize,
const float& LayerMin, const float& LayerMax,
const int32& LandscapeXSize, const int32& LandscapeYSize,
TArray<uint8>& LayerData, const bool& NoResize )
{
// Convert the float data to uint8
LayerData.SetNumUninitialized( HoudiniXSize * HoudiniYSize );
// Calculating the factor used to convert from Houdini's ZRange to [0 255]
double LayerZRange = ( LayerMax - LayerMin );
double LayerZSpacing = ( LayerZRange != 0.0 ) ? ( 255.0 / (double)( LayerZRange ) ) : 0.0;
int32 nUnrealIndex = 0;
for ( int32 nY = 0; nY < HoudiniYSize; nY++ )
{
for ( int32 nX = 0; nX < HoudiniXSize; nX++ )
{
// Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y
int32 nHoudini = nY + nX * HoudiniYSize;
// Get the double values in [0 - ZRange]
double DoubleValue = (double)FMath::Clamp(FloatLayerData[ nHoudini ], LayerMin, LayerMax) - (double)LayerMin;
// Then convert it to [0 - 255]
DoubleValue *= LayerZSpacing;
LayerData[ nUnrealIndex++ ] = FMath::RoundToInt( DoubleValue );
}
}
// Finally, resize the data to fit with the new landscape size if needed
if ( NoResize )
return true;
return FHoudiniLandscapeUtils::ResizeLayerDataForLandscape(
LayerData, HoudiniXSize, HoudiniYSize,
LandscapeXSize, LandscapeYSize );
}
bool
FHoudiniLandscapeUtils::GetNonWeightBlendedLayerNames( const FHoudiniGeoPartObject& HeightfieldGeoPartObject, TArray<FString>& NonWeightBlendedLayerNames )
{
// See if we can find the NonWeightBlendedLayer prim attribute on the heightfield
HAPI_NodeId HeightfieldNodeId = HeightfieldGeoPartObject.HapiGeoGetNodeId();
HAPI_PartInfo PartInfo;
FHoudiniApi::PartInfo_Init(&PartInfo);
if ( !HeightfieldGeoPartObject.HapiPartGetInfo( PartInfo ) )
return false;
HAPI_PartId PartId = HeightfieldGeoPartObject.GetPartId();
// Get All attribute names for that part
int32 nAttribCount = PartInfo.attributeCounts[ HAPI_ATTROWNER_PRIM ];
TArray<HAPI_StringHandle> AttribNameSHArray;
AttribNameSHArray.SetNum( nAttribCount );
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeNames(
FHoudiniEngine::Get().GetSession(),
HeightfieldNodeId, PartInfo.id, HAPI_ATTROWNER_PRIM,
AttribNameSHArray.GetData(), nAttribCount ) )
return false;
// Looking for all the attributes that starts with unreal_landscape_layer_nonweightblended
for ( int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx )
{
FString HapiString = TEXT("");
FHoudiniEngineString HoudiniEngineString( AttribNameSHArray[ Idx ] );
HoudiniEngineString.ToFString( HapiString );
if ( !HapiString.StartsWith( "unreal_landscape_layer_nonweightblended", ESearchCase::IgnoreCase ) )
continue;
// Get the Attribute Info
HAPI_AttributeInfo AttribInfo;
FHoudiniApi::AttributeInfo_Init(&AttribInfo);
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAttributeInfo(
FHoudiniEngine::Get().GetSession(),
HeightfieldNodeId, PartId, TCHAR_TO_UTF8( *HapiString ),
HAPI_ATTROWNER_PRIM, &AttribInfo ), false );
if ( AttribInfo.storage != HAPI_STORAGETYPE_STRING )
break;
// 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(),
HeightfieldNodeId, PartId, TCHAR_TO_UTF8( *HapiString ), &AttribInfo,
HapiSHArray.GetData(), 0, AttribInfo.count ), false );
// Convert them to FString
for ( int32 IdxSH = 0; IdxSH < HapiSHArray.Num(); IdxSH++ )
{
FString CurrentString;
FHoudiniEngineString HEngineString( HapiSHArray[ IdxSH ] );
HEngineString.ToFString( CurrentString );
TArray<FString> Tokens;
CurrentString.ParseIntoArray( Tokens, TEXT(" "), true );
for( int32 n = 0; n < Tokens.Num(); n++ )
NonWeightBlendedLayerNames.Add( Tokens[ n ] );
}
// We found the attribute, exit
break;
}
return true;
}
//-------------------------------------------------------------------------------------------------------------------
// From LandscapeEditorUtils.h
//
// Helpers function for FHoudiniEngineUtils::ResizeHeightDataForLandscape
//-------------------------------------------------------------------------------------------------------------------
template<typename T>
void ExpandData( T* OutData, const T* InData,
int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY,
int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY )
{
const int32 OldWidth = OldMaxX - OldMinX + 1;
const int32 OldHeight = OldMaxY - OldMinY + 1;
const int32 NewWidth = NewMaxX - NewMinX + 1;
const int32 NewHeight = NewMaxY - NewMinY + 1;
const int32 OffsetX = NewMinX - OldMinX;
const int32 OffsetY = NewMinY - OldMinY;
for ( int32 Y = 0; Y < NewHeight; ++Y )
{
const int32 OldY = FMath::Clamp<int32>( Y + OffsetY, 0, OldHeight - 1 );
// Pad anything to the left
const T PadLeft = InData[ OldY * OldWidth + 0 ];
for ( int32 X = 0; X < -OffsetX; ++X )
{
OutData[ Y * NewWidth + X ] = PadLeft;
}
// Copy one row of the old data
{
const int32 X = FMath::Max( 0, -OffsetX );
const int32 OldX = FMath::Clamp<int32>( X + OffsetX, 0, OldWidth - 1 );
FMemory::Memcpy( &OutData[ Y * NewWidth + X ], &InData[ OldY * OldWidth + OldX ], FMath::Min<int32>( OldWidth, NewWidth ) * sizeof( T ) );
}
const T PadRight = InData[ OldY * OldWidth + OldWidth - 1 ];
for ( int32 X = -OffsetX + OldWidth; X < NewWidth; ++X )
{
OutData[ Y * NewWidth + X ] = PadRight;
}
}
}
template<typename T>
TArray<T> ExpandData(const TArray<T>& Data,
int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY,
int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY,
int32* PadOffsetX = nullptr, int32* PadOffsetY = nullptr )
{
const int32 NewWidth = NewMaxX - NewMinX + 1;
const int32 NewHeight = NewMaxY - NewMinY + 1;
TArray<T> Result;
Result.Empty( NewWidth * NewHeight );
Result.AddUninitialized( NewWidth * NewHeight );
ExpandData( Result.GetData(), Data.GetData(),
OldMinX, OldMinY, OldMaxX, OldMaxY,
NewMinX, NewMinY, NewMaxX, NewMaxY );
// Return the padding so we can offset the terrain position after
if ( PadOffsetX )
*PadOffsetX = NewMinX;
if ( PadOffsetY )
*PadOffsetY = NewMinY;
return Result;
}
template<typename T>
TArray<T> ResampleData( const TArray<T>& Data, int32 OldWidth, int32 OldHeight, int32 NewWidth, int32 NewHeight )
{
TArray<T> Result;
Result.Empty( NewWidth * NewHeight );
Result.AddUninitialized( NewWidth * NewHeight );
const float XScale = (float)( OldWidth - 1 ) / ( NewWidth - 1 );
const float YScale = (float)( OldHeight - 1 ) / ( NewHeight - 1 );
for ( int32 Y = 0; Y < NewHeight; ++Y )
{
for ( int32 X = 0; X < NewWidth; ++X )
{
const float OldY = Y * YScale;
const float OldX = X * XScale;
const int32 X0 = FMath::FloorToInt( OldX );
const int32 X1 = FMath::Min( FMath::FloorToInt( OldX ) + 1, OldWidth - 1 );
const int32 Y0 = FMath::FloorToInt( OldY );
const int32 Y1 = FMath::Min( FMath::FloorToInt( OldY ) + 1, OldHeight - 1 );
const T& Original00 = Data[ Y0 * OldWidth + X0 ];
const T& Original10 = Data[ Y0 * OldWidth + X1 ];
const T& Original01 = Data[ Y1 * OldWidth + X0 ];
const T& Original11 = Data[ Y1 * OldWidth + X1 ];
Result[ Y * NewWidth + X ] = FMath::BiLerp( Original00, Original10, Original01, Original11, FMath::Fractional( OldX ), FMath::Fractional( OldY ) );
}
}
return Result;
}
//-------------------------------------------------------------------------------------------------------------------
bool
FHoudiniLandscapeUtils::CalcLandscapeSizeFromHeightfieldSize(
const int32& SizeX, const int32& SizeY,
int32& NewSizeX, int32& NewSizeY,
int32& NumberOfSectionsPerComponent,
int32& NumberOfQuadsPerSection )
{
if ( (SizeX < 2) || (SizeY < 2) )
return false;
NumberOfSectionsPerComponent = 1;
NumberOfQuadsPerSection = 1;
NewSizeX = -1;
NewSizeY = -1;
// Unreal's default sizes
int32 SectionSizes[] = { 7, 15, 31, 63, 127, 255 };
int32 NumSections[] = { 1, 2 };
// Component count used to calculate the final size of the landscape
int32 ComponentsCountX = 1;
int32 ComponentsCountY = 1;
// Lambda for clamping the number of component in X/Y
auto ClampLandscapeSize = [&]()
{
// Max size is either whole components below 8192 verts, or 32 components
ComponentsCountX = FMath::Clamp(ComponentsCountX, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumberOfSectionsPerComponent * NumberOfQuadsPerSection))));
ComponentsCountY = FMath::Clamp(ComponentsCountY, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumberOfSectionsPerComponent * NumberOfQuadsPerSection))));
};
// Try to find a section size and number of sections that exactly matches the dimensions of the heightfield
bool bFoundMatch = false;
for (int32 SectionSizesIdx = UE_ARRAY_COUNT(SectionSizes) - 1; SectionSizesIdx >= 0; SectionSizesIdx--)
{
for (int32 NumSectionsIdx = UE_ARRAY_COUNT(NumSections) - 1; NumSectionsIdx >= 0; NumSectionsIdx--)
{
int32 ss = SectionSizes[SectionSizesIdx];
int32 ns = NumSections[NumSectionsIdx];
if (((SizeX - 1) % (ss * ns)) == 0 && ((SizeX - 1) / (ss * ns)) <= 32 &&
((SizeY - 1) % (ss * ns)) == 0 && ((SizeY - 1) / (ss * ns)) <= 32)
{
bFoundMatch = true;
NumberOfQuadsPerSection = ss;
NumberOfSectionsPerComponent = ns;
ComponentsCountX = (SizeX - 1) / (ss * ns);
ComponentsCountY = (SizeY - 1) / (ss * ns);
ClampLandscapeSize();
break;
}
}
if (bFoundMatch)
{
break;
}
}
if (!bFoundMatch)
{
// if there was no exact match, try increasing the section size until we encompass the whole heightmap
const int32 CurrentSectionSize = NumberOfQuadsPerSection;
const int32 CurrentNumSections = NumberOfSectionsPerComponent;
for (int32 SectionSizesIdx = 0; SectionSizesIdx < UE_ARRAY_COUNT(SectionSizes); SectionSizesIdx++)
{
if (SectionSizes[SectionSizesIdx] < CurrentSectionSize)
{
continue;
}
const int32 ComponentsX = FMath::DivideAndRoundUp((SizeX - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections);
const int32 ComponentsY = FMath::DivideAndRoundUp((SizeY - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections);
if (ComponentsX <= 32 && ComponentsY <= 32)
{
bFoundMatch = true;
NumberOfQuadsPerSection = SectionSizes[SectionSizesIdx];
ComponentsCountX = ComponentsX;
ComponentsCountY = ComponentsY;
ClampLandscapeSize();
break;
}
}
}
if (!bFoundMatch)
{
// if the heightmap is very large, fall back to using the largest values we support
const int32 MaxSectionSize = SectionSizes[UE_ARRAY_COUNT(SectionSizes) - 1];
const int32 MaxNumSubSections = NumSections[UE_ARRAY_COUNT(NumSections) - 1];
const int32 ComponentsX = FMath::DivideAndRoundUp((SizeX - 1), MaxSectionSize * MaxNumSubSections);
const int32 ComponentsY = FMath::DivideAndRoundUp((SizeY - 1), MaxSectionSize * MaxNumSubSections);
bFoundMatch = true;
NumberOfQuadsPerSection = MaxSectionSize;
NumberOfSectionsPerComponent = MaxNumSubSections;
ComponentsCountX = ComponentsX;
ComponentsCountY = ComponentsY;
ClampLandscapeSize();
}
if (!bFoundMatch)
{
// Using default size just to not crash..
NewSizeX = 512;
NewSizeY = 512;
NumberOfSectionsPerComponent = 1;
NumberOfQuadsPerSection = 511;
ComponentsCountX = 1;
ComponentsCountY = 1;
}
else
{
// Calculating the desired size
int32 QuadsPerComponent = NumberOfSectionsPerComponent * NumberOfQuadsPerSection;
NewSizeX = ComponentsCountX * QuadsPerComponent + 1;
NewSizeY = ComponentsCountY * QuadsPerComponent + 1;
}
return bFoundMatch;
}
//-------------------------------------------------------------------------------------------------------------------
bool
FHoudiniLandscapeUtils::ResizeHeightDataForLandscape(
TArray<uint16>& HeightData,
const int32& SizeX, const int32& SizeY,
const int32& NewSizeX, const int32& NewSizeY,
FVector& LandscapeResizeFactor,
FVector& LandscapePositionOffset )
{
LandscapeResizeFactor = FVector::OneVector;
LandscapePositionOffset = FVector::ZeroVector;
if ( HeightData.Num() <= 4 )
return false;
if ( ( SizeX < 2 ) || ( SizeY < 2 ) )
return false;
// No need to resize anything
if ( SizeX == NewSizeX && SizeY == NewSizeY )
return true;
// Do we need to resize/expand the data to the new size?
bool bForceResample = false;
bool bResample = bForceResample ? true : ( ( NewSizeX <= SizeX ) && ( NewSizeY <= SizeY ) );
TArray<uint16> NewData;
if ( !bResample )
{
// Expanding the data by padding
NewData.SetNumUninitialized( NewSizeX * NewSizeY );
const int32 OffsetX = (int32)( NewSizeX - SizeX ) / 2;
const int32 OffsetY = (int32)( NewSizeY - SizeY ) / 2;
// Store the offset in pixel due to the padding
int32 PadOffsetX = 0;
int32 PadOffsetY = 0;
// Expanding the Data
NewData = ExpandData(
HeightData, 0, 0, SizeX - 1, SizeY - 1,
-OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1,
&PadOffsetX, &PadOffsetY );
// We will need to offset the landscape position due to the value added by the padding
LandscapePositionOffset.X = (float)PadOffsetX;
LandscapePositionOffset.Y = (float)PadOffsetY;
// Notify the user that the data was padded
HOUDINI_LOG_WARNING(
TEXT("Landscape data was padded from ( %d x %d ) to ( %d x %d )."),
SizeX, SizeY, NewSizeX, NewSizeY );
}
else
{
// Resampling the data
NewData.SetNumUninitialized( NewSizeX * NewSizeY );
NewData = ResampleData( HeightData, SizeX, SizeY, NewSizeX, NewSizeY );
// The landscape has been resized, we'll need to take that into account when sizing it
LandscapeResizeFactor.X = (float)SizeX / (float)NewSizeX;
LandscapeResizeFactor.Y = (float)SizeY / (float)NewSizeY;
LandscapeResizeFactor.Z = 1.0f;
// Notify the user if the heightfield data was resized
HOUDINI_LOG_WARNING(
TEXT("Landscape data was resized from ( %d x %d ) to ( %d x %d )."),
SizeX, SizeY, NewSizeX, NewSizeY );
}
// Replaces Old data with the new one
HeightData = NewData;
return true;
}
bool
FHoudiniLandscapeUtils::ResizeLayerDataForLandscape(
TArray< uint8 >& LayerData,
const int32& SizeX, const int32& SizeY,
const int32& NewSizeX, const int32& NewSizeY )
{
if ( ( NewSizeX == SizeX ) && ( NewSizeY == SizeY ) )
return true;
bool bForceResample = false;
bool bResample = bForceResample ? true : ( ( NewSizeX <= SizeX ) && ( NewSizeY <= SizeY ) );
TArray<uint8> NewData;
if (!bResample)
{
NewData.SetNumUninitialized( NewSizeX * NewSizeY );
const int32 OffsetX = (int32)( NewSizeX - SizeX ) / 2;
const int32 OffsetY = (int32)( NewSizeY - SizeY ) / 2;
// Expanding the Data
NewData = ExpandData(
LayerData,
0, 0, SizeX - 1, SizeY - 1,
-OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1 );
}
else
{
// Resampling the data
NewData.SetNumUninitialized( NewSizeX * NewSizeY );
NewData = ResampleData( LayerData, SizeX, SizeY, NewSizeX, NewSizeY );
}
LayerData = NewData;
return true;
}
#if WITH_EDITOR
bool
FHoudiniLandscapeUtils::CreateHeightfieldFromLandscape(
ALandscapeProxy* LandscapeProxy, HAPI_NodeId& CreatedHeightfieldNodeId )
{
if ( !LandscapeProxy )
return false;
// Export the whole landscape and its layer as a single heightfield
//--------------------------------------------------------------------------------------------------
// 1. Extracting the height data
//--------------------------------------------------------------------------------------------------
TArray<uint16> HeightData;
int32 XSize, YSize;
FVector Min, Max;
if ( !GetLandscapeData( LandscapeProxy, HeightData, XSize, YSize, Min, Max ) )
return false;
//--------------------------------------------------------------------------------------------------
// 2. Convert the height uint16 data to float
//--------------------------------------------------------------------------------------------------
TArray<float> HeightfieldFloatValues;
HAPI_VolumeInfo HeightfieldVolumeInfo;
FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo);
FTransform LandscapeTransform = LandscapeProxy->ActorToWorld();
FVector CenterOffset = FVector::ZeroVector;
if ( !ConvertLandscapeDataToHeightfieldData(
HeightData, XSize, YSize, Min, Max, LandscapeTransform,
HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset ) )
return false;
//--------------------------------------------------------------------------------------------------
// 3. Create the Heightfield Input Node
//--------------------------------------------------------------------------------------------------
HAPI_NodeId HeightFieldId = -1;
HAPI_NodeId HeightId = -1;
HAPI_NodeId MaskId = -1;
HAPI_NodeId MergeId = -1;
if ( !CreateHeightfieldInputNode( -1, LandscapeProxy->GetName(), XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId ) )
return false;
//--------------------------------------------------------------------------------------------------
// 4. Set the HeightfieldData in Houdini
//--------------------------------------------------------------------------------------------------
// Set the Height volume's data
HAPI_PartId PartId = 0;
if ( !SetHeighfieldData( HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height") ) )
return false;
// Add the materials used
UMaterialInterface* LandscapeMat = LandscapeProxy->GetLandscapeMaterial();
UMaterialInterface* LandscapeHoleMat = LandscapeProxy->GetLandscapeHoleMaterial();
AddLandscapeMaterialAttributesToVolume( HeightId, PartId, LandscapeMat, LandscapeHoleMat );
// Add the landscape's actor tags as prim attributes if we have any
FHoudiniEngineUtils::CreateGroupOrAttributeFromTags(HeightId, PartId, LandscapeProxy->Tags, true);
// Commit the height volume
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(
FHoudiniEngine::Get().GetSession(), HeightId ), false );
//--------------------------------------------------------------------------------------------------
// 5. Extract and convert all the layers
//--------------------------------------------------------------------------------------------------
ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo();
if ( !LandscapeInfo )
return false;
bool MaskInitialized = false;
int32 MergeInputIndex = 2;
int32 NumLayers = LandscapeInfo->Layers.Num();
for ( int32 n = 0; n < NumLayers; n++ )
{
// 1. Extract the uint8 values from the layer
TArray<uint8> CurrentLayerIntData;
FLinearColor LayerUsageDebugColor;
FString LayerName;
if ( !GetLandscapeLayerData( LandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName ) )
continue;
// 2. Convert unreal uint8 values to floats
// If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float
HAPI_VolumeInfo CurrentLayerVolumeInfo;
FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo);
TArray < float > CurrentLayerFloatData;
if ( !ConvertLandscapeLayerDataToHeightfieldData(
CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor,
CurrentLayerFloatData, CurrentLayerVolumeInfo ) )
continue;
// We reuse the height layer's transform
CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform;
// 3. See if we need to create an input volume, or can reuse the HF's default mask volume
bool IsMask = false;
if ( LayerName.Equals( TEXT("mask"), ESearchCase::IgnoreCase ) )
IsMask = true;
HAPI_NodeId LayerVolumeNodeId = -1;
if ( !IsMask )
{
// Current layer is not mask, so we need to create a new input volume
std::string LayerNameStr;
FHoudiniEngineUtils::ConvertUnrealString(LayerName, LayerNameStr);
FHoudiniApi::CreateHeightfieldInputVolumeNode(
FHoudiniEngine::Get().GetSession(),
HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f );
}
else
{
// Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node
LayerVolumeNodeId = MaskId;
}
// Check if we have a valid id for the input volume.
if ( !FHoudiniEngineUtils::IsHoudiniNodeValid( LayerVolumeNodeId ) )
continue;
// 4. Set the layer/mask heighfield data in Houdini
HAPI_PartId CurrentPartId = 0;
if ( !SetHeighfieldData( LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName ) )
continue;
// Also add the material attributes to the layer volumes
AddLandscapeMaterialAttributesToVolume(LayerVolumeNodeId, PartId, LandscapeMat, LandscapeHoleMat);
// Add the landscape's actor tags as prim attributes if we have any
FHoudiniEngineUtils::CreateGroupOrAttributeFromTags(LayerVolumeNodeId, PartId, LandscapeProxy->Tags, true);
// Commit the volume's geo
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(
FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId ), false);
if ( !IsMask )
{
// We had to create a new volume for this layer, so we need to connect it to the HF's merge node
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput(
FHoudiniEngine::Get().GetSession(),
MergeId, MergeInputIndex, LayerVolumeNodeId, 0 ), false);
MergeInputIndex++;
}
else
{
MaskInitialized = true;
}
}
// We need to have a mask layer as it is required for proper heightfield functionalities
// Setting the volume info on the mask is needed for the HF to have proper transform in H!
// If we didn't create a mask volume before, send a default one now
if (!MaskInitialized)
{
MaskInitialized = InitDefaultHeightfieldMask( HeightfieldVolumeInfo, MaskId );
// Add the materials used
AddLandscapeMaterialAttributesToVolume( MaskId, PartId, LandscapeMat, LandscapeHoleMat );
// Add the landscape's actor tags as prim attributes if we have any
FHoudiniEngineUtils::CreateGroupOrAttributeFromTags(MaskId, PartId, LandscapeProxy->Tags, true);
// Commit the mask volume's geo
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(
FHoudiniEngine::Get().GetSession(), MaskId ), false );
}
HAPI_TransformEuler HAPIObjectTransform;
FHoudiniApi::TransformEuler_Init(&HAPIObjectTransform);
//FMemory::Memzero< HAPI_TransformEuler >( HAPIObjectTransform );
LandscapeTransform.SetScale3D(FVector::OneVector);
FHoudiniEngineUtils::TranslateUnrealTransform( LandscapeTransform, HAPIObjectTransform );
HAPIObjectTransform.position[1] = 0.0f;
HAPI_NodeId ParentObjNodeId = FHoudiniEngineUtils::HapiGetParentNodeId( HeightFieldId );
FHoudiniApi::SetObjectTransform(FHoudiniEngine::Get().GetSession(), ParentObjNodeId, &HAPIObjectTransform);
// Since HF are centered but landscape aren't, we need to set the HF's center parameter
FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 0, CenterOffset.X);
FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 1, 0.0);
FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 2, CenterOffset.Y);
// Finally, cook the Heightfield node
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode(
FHoudiniEngine::Get().GetSession(), HeightFieldId, nullptr), false );
CreatedHeightfieldNodeId = HeightFieldId;
return true;
}
bool
FHoudiniLandscapeUtils::CreateHeightfieldFromLandscapeComponentArray(
ALandscapeProxy* LandscapeProxy,
TSet< ULandscapeComponent * >& LandscapeComponentArray,
HAPI_NodeId& CreatedHeightfieldNodeId )
{
if ( LandscapeComponentArray.Num() <= 0 )
return false;
//--------------------------------------------------------------------------------------------------
// Each selected component will be exported as tiled volumes in a single heightfield
//--------------------------------------------------------------------------------------------------
FTransform LandscapeTransform = LandscapeProxy->GetTransform();
//
HAPI_NodeId HeightfieldNodeId = -1;
HAPI_NodeId HeightfieldeMergeId = -1;
int32 MergeInputIndex = 0;
bool bAllComponentCreated = true;
for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++)
{
ULandscapeComponent * CurrentComponent = LandscapeProxy->LandscapeComponents[ ComponentIdx ];
if ( !CurrentComponent )
continue;
if ( !LandscapeComponentArray.Contains( CurrentComponent ) )
continue;
if ( !CreateHeightfieldFromLandscapeComponent( CurrentComponent, ComponentIdx, HeightfieldNodeId, HeightfieldeMergeId, MergeInputIndex ) )
bAllComponentCreated = false;
}
// Check that we have a valid id for the input Heightfield.
if ( FHoudiniEngineUtils::IsHoudiniNodeValid( HeightfieldNodeId ) )
CreatedHeightfieldNodeId = HeightfieldNodeId;
// Set the HF's parent OBJ's tranform to the Landscape's transform
HAPI_TransformEuler HAPIObjectTransform;
FHoudiniApi::TransformEuler_Init(&HAPIObjectTransform);
//FMemory::Memzero< HAPI_TransformEuler >( HAPIObjectTransform );
LandscapeTransform.SetScale3D( FVector::OneVector );
FHoudiniEngineUtils::TranslateUnrealTransform( LandscapeTransform, HAPIObjectTransform );
HAPIObjectTransform.position[ 1 ] = 0.0f;
HAPI_NodeId ParentObjNodeId = FHoudiniEngineUtils::HapiGetParentNodeId( HeightfieldNodeId );
FHoudiniApi::SetObjectTransform( FHoudiniEngine::Get().GetSession(), ParentObjNodeId, &HAPIObjectTransform );
return bAllComponentCreated;
}
bool
FHoudiniLandscapeUtils::CreateHeightfieldFromLandscapeComponent(
ULandscapeComponent * LandscapeComponent,
const int32& ComponentIndex,
HAPI_NodeId& HeightFieldId,
HAPI_NodeId& MergeId,
int32& MergeInputIndex )
{
if ( !LandscapeComponent )
return false;
//--------------------------------------------------------------------------------------------------
// 1. Extract the height data
//--------------------------------------------------------------------------------------------------
int32 MinX = MAX_int32;
int32 MinY = MAX_int32;
int32 MaxX = -MAX_int32;
int32 MaxY = -MAX_int32;
LandscapeComponent->GetComponentExtent( MinX, MinY, MaxX, MaxY );
ULandscapeInfo* LandscapeInfo = LandscapeComponent->GetLandscapeInfo();
if ( !LandscapeInfo )
return false;
TArray<uint16> HeightData;
int32 XSize, YSize;
if ( !GetLandscapeData( LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize ) )
return false;
FVector Origin = LandscapeComponent->Bounds.Origin;
FVector Extents = LandscapeComponent->Bounds.BoxExtent;
FVector Min = Origin - Extents;
FVector Max = Origin + Extents;
//--------------------------------------------------------------------------------------------------
// 2. Convert the landscape's height uint16 data to float
//--------------------------------------------------------------------------------------------------
TArray<float> HeightfieldFloatValues;
HAPI_VolumeInfo HeightfieldVolumeInfo;
FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo);
FTransform LandscapeComponentTransform = LandscapeComponent->GetComponentTransform();
FVector CenterOffset = FVector::ZeroVector;
if ( !ConvertLandscapeDataToHeightfieldData(
HeightData, XSize, YSize, Min, Max, LandscapeComponentTransform,
HeightfieldFloatValues, HeightfieldVolumeInfo,
CenterOffset ) )
return false;
// We need to modify the Volume's position to the Component's position relative to the Landscape's position
FVector RelativePosition = LandscapeComponent->GetRelativeTransform().GetLocation();
HeightfieldVolumeInfo.transform.position[1] = RelativePosition.X;
HeightfieldVolumeInfo.transform.position[0] = RelativePosition.Y;
HeightfieldVolumeInfo.transform.position[2] = 0.0f;
//--------------------------------------------------------------------------------------------------
// 3. Create the Heightfield Input Node
//--------------------------------------------------------------------------------------------------
HAPI_NodeId HeightId = -1;
HAPI_NodeId MaskId = -1;
bool CreatedHeightfieldNode = false;
if ( HeightFieldId < 0 || MergeId < 0 )
{
// We haven't created the HF input node yet, do it now
if (!CreateHeightfieldInputNode(-1, TEXT("LandscapeComponents"), XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId))
return false;
MergeInputIndex = 2;
CreatedHeightfieldNode = true;
}
else
{
// Heightfield node was previously created, create additionnal height and a mask volumes for it
FHoudiniApi::CreateHeightfieldInputVolumeNode(
FHoudiniEngine::Get().GetSession(),
HeightFieldId, &HeightId, "height", XSize, YSize, 1.0f );
FHoudiniApi::CreateHeightfieldInputVolumeNode(
FHoudiniEngine::Get().GetSession(),
HeightFieldId, &MaskId, "mask", XSize, YSize, 1.0f );
// Connect the two newly created volumes to the HF's merge node
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput(
FHoudiniEngine::Get().GetSession(),
MergeId, MergeInputIndex++, HeightId, 0 ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput(
FHoudiniEngine::Get().GetSession(),
MergeId, MergeInputIndex++, MaskId, 0 ), false );
}
//--------------------------------------------------------------------------------------------------
// 4. Set the HeightfieldData in Houdini
//--------------------------------------------------------------------------------------------------
// Set the Height volume's data
HAPI_PartId PartId = 0;
if (!SetHeighfieldData( HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height") ) )
return false;
// Add the materials used
UMaterialInterface* LandscapeMat = LandscapeComponent->GetLandscapeMaterial();
UMaterialInterface* LandscapeHoleMat = LandscapeComponent->GetLandscapeHoleMaterial();
AddLandscapeMaterialAttributesToVolume(HeightId, PartId, LandscapeMat, LandscapeHoleMat);
// Add the tile attribute
AddLandscapeTileAttribute(HeightId, PartId, ComponentIndex);
// Add the landscape component extent attribute
AddLandscapeComponentExtentAttributes( HeightId, PartId, MinX, MaxX, MinY, MaxY );
// Add the component's tag as prim attributes if we have any
FHoudiniEngineUtils::CreateGroupOrAttributeFromTags(HeightId, PartId, LandscapeComponent->ComponentTags, true);
// Commit the height volume
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo(
FHoudiniEngine::Get().GetSession(), HeightId), false);
//--------------------------------------------------------------------------------------------------
// 4. Extract and convert all the layers to HF masks
//--------------------------------------------------------------------------------------------------
bool MaskInitialized = false;
int32 NumLayers = LandscapeInfo->Layers.Num();
for ( int32 n = 0; n < NumLayers; n++ )
{
// 1. Extract the uint8 values from the layer
TArray<uint8> CurrentLayerIntData;
FLinearColor LayerUsageDebugColor;
FString LayerName;
if ( !GetLandscapeLayerData(LandscapeInfo, n, MinX, MinY, MaxX, MaxY, CurrentLayerIntData, LayerUsageDebugColor, LayerName) )
continue;
// 2. Convert unreal uint8 to float
// If the layer came from Houdini, additional info might have been stored in the DebugColor
HAPI_VolumeInfo CurrentLayerVolumeInfo;
FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo);
TArray < float > CurrentLayerFloatData;
if ( !ConvertLandscapeLayerDataToHeightfieldData(
CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor,
CurrentLayerFloatData, CurrentLayerVolumeInfo ) )
continue;
// We reuse the transform used for the height volume
CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform;
// 3. See if we need to create an input volume, or if we can reuse the HF's default mask volume
bool IsMask = false;
if ( LayerName.Equals( TEXT("mask"), ESearchCase::IgnoreCase ) )
IsMask = true;
HAPI_NodeId LayerVolumeNodeId = -1;
if ( !IsMask )
{
// Current layer is not mask, so we need to create a new input volume
std::string LayerNameStr;
FHoudiniEngineUtils::ConvertUnrealString( LayerName, LayerNameStr );
FHoudiniApi::CreateHeightfieldInputVolumeNode(
FHoudiniEngine::Get().GetSession(),
HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f );
}
else
{
// Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node
LayerVolumeNodeId = MaskId;
}
// Check if we have a valid id for the input volume.
if ( !FHoudiniEngineUtils::IsHoudiniNodeValid( LayerVolumeNodeId ) )
continue;
// 4. Set the layer/mask heighfield data in Houdini
HAPI_PartId CurrentPartId = 0;
if ( !SetHeighfieldData( LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName ) )
continue;
// Add the materials used
AddLandscapeMaterialAttributesToVolume( LayerVolumeNodeId, PartId, LandscapeMat, LandscapeHoleMat );
// Add the tile attribute
AddLandscapeTileAttribute( LayerVolumeNodeId, PartId, ComponentIndex );
// Add the landscape component extent attribute
AddLandscapeComponentExtentAttributes( LayerVolumeNodeId, PartId, MinX, MaxX, MinY, MaxY );
// Add the component's tag as prim attributes if we have any
FHoudiniEngineUtils::CreateGroupOrAttributeFromTags(LayerVolumeNodeId, PartId, LandscapeComponent->ComponentTags, true);
// Commit the volume's geo
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(
FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId ), false);
if ( !IsMask )
{
// We had to create a new volume for this layer, so we need to connect it to the HF's merge node
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput(
FHoudiniEngine::Get().GetSession(),
MergeId, MergeInputIndex, LayerVolumeNodeId, 0 ), false);
MergeInputIndex++;
}
else
{
MaskInitialized = true;
}
}
// We need to have a mask layer as it is required for proper heightfield functionalities
// Setting the volume info on the mask is needed for the HF to have proper transform in H!
// If we didn't create a mask volume before, send a default one now
if (!MaskInitialized)
{
MaskInitialized = InitDefaultHeightfieldMask( HeightfieldVolumeInfo, MaskId );
// Add the materials used
AddLandscapeMaterialAttributesToVolume( MaskId, PartId, LandscapeMat, LandscapeHoleMat );
// Add the tile attribute
AddLandscapeTileAttribute( MaskId, PartId, ComponentIndex );
// Add the landscape component extent attribute
AddLandscapeComponentExtentAttributes( MaskId, PartId, MinX, MaxX, MinY, MaxY );
// Add the component's tag as prim attributes if we have any
FHoudiniEngineUtils::CreateGroupOrAttributeFromTags(MaskId, PartId, LandscapeComponent->ComponentTags, true);
// Commit the mask volume's geo
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo(
FHoudiniEngine::Get().GetSession(), MaskId), false);
}
if ( CreatedHeightfieldNode )
{
// Since HF are centered but landscape arent, we need to set the HF's center parameter
// Do it only once after creating the Heightfield node
FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 0, CenterOffset.X);
FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 1, 0.0);
FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 2, CenterOffset.Y);
}
// Finally, cook the Heightfield node
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode(
FHoudiniEngine::Get().GetSession(), HeightFieldId, nullptr), false);
return true;
}
bool
FHoudiniLandscapeUtils::GetLandscapeData(
ALandscape* Landscape,
TArray<uint16>& HeightData,
int32& XSize, int32& YSize,
FVector& Min, FVector& Max )
{
if ( !Landscape )
return false;
ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo();
if ( !LandscapeInfo )
return false;
// Get the landscape extents to get its size
int32 MinX = MAX_int32;
int32 MinY = MAX_int32;
int32 MaxX = -MAX_int32;
int32 MaxY = -MAX_int32;
if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY))
return false;
if ( !GetLandscapeData( LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize ) )
return false;
// Get the landscape Min/Max values
// Do not use Landscape->GetActorBounds() here as instanced geo
// (due to grass layers for example) can cause it to return incorrect bounds!
FVector Origin, Extent;
GetLandscapeActorBounds(Landscape, Origin, Extent);
// Get the landscape Min/Max values
Min = Origin - Extent;
Max = Origin + Extent;
return true;
}
bool
FHoudiniLandscapeUtils::GetLandscapeData(
ALandscapeProxy* LandscapeProxy,
TArray<uint16>& HeightData,
int32& XSize, int32& YSize,
FVector& Min, FVector& Max)
{
if (!LandscapeProxy)
return false;
ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo();
if (!LandscapeInfo)
return false;
// Get the landscape extents to get its size
int32 MinX = MAX_int32;
int32 MinY = MAX_int32;
int32 MaxX = -MAX_int32;
int32 MaxY = -MAX_int32;
// To handle streaming proxies correctly, get the extents via all the components,
// not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies.
for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents)
{
Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY);
}
if (!GetLandscapeData(LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize))
return false;
// Get the landscape Min/Max values
// Do not use Landscape->GetActorBounds() here as instanced geo
// (due to grass layers for example) can cause it to return incorrect bounds!
FVector Origin, Extent;
GetLandscapeProxyBounds(LandscapeProxy, Origin, Extent);
// Get the landscape Min/Max values
Min = Origin - Extent;
Max = Origin + Extent;
return true;
}
void
FHoudiniLandscapeUtils::GetLandscapeActorBounds(
ALandscape* Landscape, FVector& Origin, FVector& Extents )
{
// Iterate only on the landscape components
FBox Bounds(ForceInit);
for (const UActorComponent* ActorComponent : Landscape->GetComponents())
{
const ULandscapeComponent* LandscapeComp = Cast<const ULandscapeComponent>(ActorComponent);
if ( LandscapeComp && LandscapeComp->IsRegistered() )
Bounds += LandscapeComp->Bounds.GetBox();
}
// Convert the bounds to origin/offset vectors
Bounds.GetCenterAndExtents(Origin, Extents);
}
void
FHoudiniLandscapeUtils::GetLandscapeProxyBounds(
ALandscapeProxy* LandscapeProxy, FVector& Origin, FVector& Extents)
{
// Iterate only on the landscape components
FBox Bounds(ForceInit);
for (const UActorComponent* ActorComponent : LandscapeProxy->GetComponents())
{
const ULandscapeComponent* LandscapeComp = Cast<const ULandscapeComponent>(ActorComponent);
if (LandscapeComp && LandscapeComp->IsRegistered())
Bounds += LandscapeComp->Bounds.GetBox();
}
// Convert the bounds to origin/offset vectors
Bounds.GetCenterAndExtents(Origin, Extents);
}
bool
FHoudiniLandscapeUtils::GetLandscapeData(
ULandscapeInfo* LandscapeInfo,
const int32& MinX, const int32& MinY,
const int32& MaxX, const int32& MaxY,
TArray<uint16>& HeightData,
int32& XSize, int32& YSize )
{
if ( !LandscapeInfo )
return false;
// Get the X/Y size in points
XSize = ( MaxX - MinX + 1 );
YSize = ( MaxY - MinY + 1 );
if ( ( XSize < 2 ) || ( YSize < 2 ) )
return false;
// Extracting the uint16 values from the landscape
FLandscapeEditDataInterface LandscapeEdit( LandscapeInfo );
HeightData.AddZeroed( XSize * YSize );
LandscapeEdit.GetHeightDataFast( MinX, MinY, MaxX, MaxY, HeightData.GetData(), 0 );
return true;
}
bool
FHoudiniLandscapeUtils::GetLandscapeLayerData(
ULandscapeInfo* LandscapeInfo, const int32& LayerIndex,
TArray<uint8>& LayerData, FLinearColor& LayerUsageDebugColor,
FString& LayerName )
{
if ( !LandscapeInfo )
return false;
// Get the landscape X/Y Size
int32 MinX = MAX_int32;
int32 MinY = MAX_int32;
int32 MaxX = -MAX_int32;
int32 MaxY = -MAX_int32;
if ( !LandscapeInfo->GetLandscapeExtent( MinX, MinY, MaxX, MaxY ) )
return false;
if ( !GetLandscapeLayerData(
LandscapeInfo, LayerIndex,
MinX, MinY, MaxX, MaxY,
LayerData, LayerUsageDebugColor, LayerName ) )
return false;
return true;
}
bool
FHoudiniLandscapeUtils::GetLandscapeLayerData(
ULandscapeInfo* LandscapeInfo,
const int32& LayerIndex,
const int32& MinX, const int32& MinY,
const int32& MaxX, const int32& MaxY,
TArray<uint8>& LayerData,
FLinearColor& LayerUsageDebugColor,
FString& LayerName )
{
if ( !LandscapeInfo )
return false;
if ( !LandscapeInfo->Layers.IsValidIndex( LayerIndex ) )
return false;
FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[ LayerIndex ];
ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj;
if (!LayerInfo)
return false;
// Calc the X/Y size in points
int32 XSize = (MaxX - MinX + 1);
int32 YSize = (MaxY - MinY + 1);
if ( (XSize < 2) || (YSize < 2) )
return false;
// extracting the uint8 values from the layer
FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo);
LayerData.AddZeroed(XSize * YSize);
LandscapeEdit.GetWeightDataFast(LayerInfo, MinX, MinY, MaxX, MaxY, LayerData.GetData(), 0);
LayerUsageDebugColor = LayerInfo->LayerUsageDebugColor;
LayerName = LayersSetting.GetLayerName().ToString();
return true;
}
#endif
bool
FHoudiniLandscapeUtils::ConvertLandscapeDataToHeightfieldData(
const TArray<uint16>& IntHeightData,
const int32& XSize, const int32& YSize,
FVector Min, FVector Max,
const FTransform& LandscapeTransform,
TArray<float>& HeightfieldFloatValues,
HAPI_VolumeInfo& HeightfieldVolumeInfo,
FVector& CenterOffset)
{
HeightfieldFloatValues.Empty();
int32 HoudiniXSize = YSize;
int32 HoudiniYSize = XSize;
int32 SizeInPoints = HoudiniXSize * HoudiniYSize;
if ( (HoudiniXSize < 2) || (HoudiniYSize < 2) )
return false;
if ( IntHeightData.Num() != SizeInPoints )
return false;
// Use default unreal scaling for marshalling landscapes
// A lot of precision will be lost in order to keep the same transform as the landscape input
bool bUseDefaultUE4Scaling = false;
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling)
bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling;
//--------------------------------------------------------------------------------------------------
// 1. Convert values to float
//--------------------------------------------------------------------------------------------------
// Convert the min/max values from cm to meters
Min /= 100.0;
Max /= 100.0;
// Unreal's landscape uses 16bits precision and range from -256m to 256m with the default scale of 100.0
// To convert the uint16 values to float "metric" values, offset the int by 32768 to center it,
// then scale it
// Spacing used to convert from uint16 to meters
double ZSpacing = 512.0 / ((double)UINT16_MAX);
ZSpacing *= ( (double)LandscapeTransform.GetScale3D().Z / 100.0 );
// Center value in meters (Landscape ranges from [-255:257] meters at default scale
double ZCenterOffset = 32767;
double ZPositionOffset = LandscapeTransform.GetLocation().Z / 100.0f;
// Convert the Int data to Float
HeightfieldFloatValues.SetNumUninitialized( SizeInPoints );
for ( int32 nY = 0; nY < HoudiniYSize; nY++ )
{
for ( int32 nX = 0; nX < HoudiniXSize; nX++ )
{
// We need to invert X/Y when reading the value from Unreal
int32 nHoudini = nX + nY * HoudiniXSize;
int32 nUnreal = nY + nX * XSize;
// Convert the int values to meter
// Unreal's digit value have a zero value of 32768
double DoubleValue = ((double)IntHeightData[nUnreal] - ZCenterOffset) * ZSpacing + ZPositionOffset;
HeightfieldFloatValues[nHoudini] = (float)DoubleValue;
}
}
//--------------------------------------------------------------------------------------------------
// 2. Convert the Unreal Transform to a HAPI_transform
//--------------------------------------------------------------------------------------------------
HAPI_Transform HapiTransform;
FHoudiniApi::Transform_Init(&HapiTransform);
//FMemory::Memzero< HAPI_Transform >( HapiTransform );
{
FQuat Rotation = LandscapeTransform.GetRotation();
if ( Rotation != FQuat::Identity )
{
//Swap(ObjectRotation.Y, ObjectRotation.Z);
HapiTransform.rotationQuaternion[0] = Rotation.X;
HapiTransform.rotationQuaternion[1] = Rotation.Z;
HapiTransform.rotationQuaternion[2] = Rotation.Y;
HapiTransform.rotationQuaternion[3] = -Rotation.W;
}
else
{
HapiTransform.rotationQuaternion[0] = 0;
HapiTransform.rotationQuaternion[1] = 0;
HapiTransform.rotationQuaternion[2] = 0;
HapiTransform.rotationQuaternion[3] = 1;
}
// Heightfield are centered, landscapes are not
CenterOffset = (Max - Min) * 0.5f;
// Unreal XYZ becomes Houdini YXZ (since heightfields are also rotated due the ZX transform)
//FVector Position = LandscapeTransform.GetLocation() / 100.0f;
HapiTransform.position[ 1 ] = 0.0f;//Position.X + CenterOffset.X;
HapiTransform.position[ 0 ] = 0.0f;//Position.Y + CenterOffset.Y;
HapiTransform.position[ 2 ] = 0.0f;
FVector Scale = LandscapeTransform.GetScale3D() / 100.0f;
HapiTransform.scale[ 0 ] = Scale.X * 0.5f * HoudiniXSize;
HapiTransform.scale[ 1 ] = Scale.Y * 0.5f * HoudiniYSize;
HapiTransform.scale[ 2 ] = 0.5f;
if ( bUseDefaultUE4Scaling )
HapiTransform.scale[2] *= Scale.Z;
HapiTransform.shear[ 0 ] = 0.0f;
HapiTransform.shear[ 1 ] = 0.0f;
HapiTransform.shear[ 2 ] = 0.0f;
}
//--------------------------------------------------------------------------------------------------
// 3. Fill the volume info
//--------------------------------------------------------------------------------------------------
HeightfieldVolumeInfo.xLength = HoudiniXSize;
HeightfieldVolumeInfo.yLength = HoudiniYSize;
HeightfieldVolumeInfo.zLength = 1;
HeightfieldVolumeInfo.minX = 0;
HeightfieldVolumeInfo.minY = 0;
HeightfieldVolumeInfo.minZ = 0;
HeightfieldVolumeInfo.transform = HapiTransform;
HeightfieldVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI;
HeightfieldVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT;
HeightfieldVolumeInfo.tupleSize = 1;
HeightfieldVolumeInfo.tileSize = 1;
HeightfieldVolumeInfo.hasTaper = false;
HeightfieldVolumeInfo.xTaper = 0.0;
HeightfieldVolumeInfo.yTaper = 0.0;
return true;
}
// Converts Unreal uint16 values to Houdini Float
bool
FHoudiniLandscapeUtils::ConvertLandscapeLayerDataToHeightfieldData(
const TArray<uint8>& IntHeightData,
const int32& XSize, const int32& YSize,
const FLinearColor& LayerUsageDebugColor,
TArray<float>& LayerFloatValues,
HAPI_VolumeInfo& LayerVolumeInfo)
{
LayerFloatValues.Empty();
int32 HoudiniXSize = YSize;
int32 HoudiniYSize = XSize;
int32 SizeInPoints = HoudiniXSize * HoudiniYSize;
if ( ( HoudiniXSize < 2 ) || ( HoudiniYSize < 2 ) )
return false;
if ( IntHeightData.Num() != SizeInPoints )
return false;
//--------------------------------------------------------------------------------------------------
// 1. Convert values to float
//--------------------------------------------------------------------------------------------------
// We need the ZMin / ZMax unt8 values
uint8 IntMin = IntHeightData[ 0 ];
uint8 IntMax = IntMin;
for ( int n = 0; n < IntHeightData.Num(); n++ )
{
if ( IntHeightData[ n ] < IntMin )
IntMin = IntHeightData[ n ];
if ( IntHeightData[ n ] > IntMax )
IntMax = IntHeightData[ n ];
}
// The range in Digits
double DigitRange = (double)IntMax - (double)IntMin;
// By default, the values will be converted to [0, 1]
float LayerMin = 0.0f;
float LayerMax = 1.0f;
float LayerSpacing = 1.0f / DigitRange;
// If this layer came from Houdini, its alpha value should be PI
// So we can extract the additionnal infos stored its debug usage color
if ( LayerUsageDebugColor.A == PI )
{
LayerMin = LayerUsageDebugColor.R;
LayerMax = LayerUsageDebugColor.G;
LayerSpacing = LayerUsageDebugColor.B;
}
LayerSpacing = ( LayerMax - LayerMin ) / DigitRange;
// Convert the Int data to Float
LayerFloatValues.SetNumUninitialized( SizeInPoints );
for ( int32 nY = 0; nY < HoudiniYSize; nY++ )
{
for ( int32 nX = 0; nX < HoudiniXSize; nX++ )
{
// We need to invert X/Y when reading the value from Unreal
int32 nHoudini = nX + nY * HoudiniXSize;
int32 nUnreal = nY + nX * XSize;
// Convert the int values to meter
// Unreal's digit value have a zero value of 32768
double DoubleValue = ( (double)IntHeightData[ nUnreal ] - (double)IntMin ) * LayerSpacing + LayerMin;
LayerFloatValues[ nHoudini ] = (float)DoubleValue;
}
}
// Verifying the converted ZMin / ZMax
float FloatMin = LayerFloatValues[ 0 ];
float FloatMax = FloatMin;
for ( int32 n = 0; n < LayerFloatValues.Num(); n++ )
{
if ( LayerFloatValues[ n ] < FloatMin )
FloatMin = LayerFloatValues[ n ];
if ( LayerFloatValues[ n ] > FloatMax )
FloatMax = LayerFloatValues[ n ];
}
//--------------------------------------------------------------------------------------------------
// 2. Fill the volume info
//--------------------------------------------------------------------------------------------------
LayerVolumeInfo.xLength = HoudiniXSize;
LayerVolumeInfo.yLength = HoudiniYSize;
LayerVolumeInfo.zLength = 1;
LayerVolumeInfo.minX = 0;
LayerVolumeInfo.minY = 0;
LayerVolumeInfo.minZ = 0;
LayerVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI;
LayerVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT;
LayerVolumeInfo.tupleSize = 1;
LayerVolumeInfo.tileSize = 1;
LayerVolumeInfo.hasTaper = false;
LayerVolumeInfo.xTaper = 0.0;
LayerVolumeInfo.yTaper = 0.0;
// The layer transform will have to be copied from the main heightfield's transform
return true;
}
bool
FHoudiniLandscapeUtils::SetHeighfieldData(
const HAPI_NodeId& VolumeNodeId,
const HAPI_PartId& PartId,
TArray<float>& FloatValues,
const HAPI_VolumeInfo& VolumeInfo,
const FString& HeightfieldName )
{
// Cook the node to get proper infos on it
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode(
FHoudiniEngine::Get().GetSession(), VolumeNodeId, nullptr ), false );
// Read the geo/part/volume info from the volume node
HAPI_GeoInfo GeoInfo;
FHoudiniApi::GeoInfo_Init(&GeoInfo);
//FMemory::Memset< HAPI_GeoInfo >(GeoInfo, 0);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo(
FHoudiniEngine::Get().GetSession(),
VolumeNodeId, &GeoInfo), false);
HAPI_PartInfo PartInfo;
FHoudiniApi::PartInfo_Init(&PartInfo);
//FMemory::Memset< HAPI_PartInfo >(PartInfo, 0);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo(
FHoudiniEngine::Get().GetSession(),
GeoInfo.nodeId, PartId, &PartInfo), false);
// Update the volume infos
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetVolumeInfo(
FHoudiniEngine::Get().GetSession(),
VolumeNodeId, PartInfo.id, &VolumeInfo), false );
// Volume name
std::string NameStr;
FHoudiniEngineUtils::ConvertUnrealString( HeightfieldName, NameStr );
// Set the Heighfield data on the volume
float * HeightData = FloatValues.GetData();
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetHeightFieldData(
FHoudiniEngine::Get().GetSession(),
GeoInfo.nodeId, PartInfo.id, NameStr.c_str(), HeightData, 0, FloatValues.Num() ), false );
return true;
}
bool
FHoudiniLandscapeUtils::CreateHeightfieldInputNode(
const HAPI_NodeId& ParentNodeId, const FString& NodeName, const int32& XSize, const int32& YSize,
HAPI_NodeId& HeightfieldNodeId, HAPI_NodeId& HeightNodeId, HAPI_NodeId& MaskNodeId, HAPI_NodeId& MergeNodeId )
{
// Make sure the Heightfield node doesnt already exists
if (HeightfieldNodeId != -1)
return false;
// Convert the node's name
std::string NameStr;
FHoudiniEngineUtils::ConvertUnrealString(NodeName, NameStr);
// Create the heigthfield node via HAPI
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateHeightfieldInputNode(
FHoudiniEngine::Get().GetSession(),
ParentNodeId, NameStr.c_str(), XSize, YSize, 1.0f,
&HeightfieldNodeId, &HeightNodeId, &MaskNodeId, &MergeNodeId ), false);
// Cook it
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode(
FHoudiniEngine::Get().GetSession(), HeightfieldNodeId, nullptr), false);
return true;
}
bool
FHoudiniLandscapeUtils::DestroyLandscapeAssetNode( HAPI_NodeId& ConnectedAssetId, TArray<HAPI_NodeId>& CreatedInputAssetIds )
{
HAPI_AssetInfo NodeAssetInfo;
FHoudiniApi::AssetInfo_Init(&NodeAssetInfo);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo(
FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &NodeAssetInfo ), false );
FHoudiniEngineString AssetOpName( NodeAssetInfo.fullOpNameSH );
FString OpName;
if ( !AssetOpName.ToFString( OpName ) )
return false;
if ( !OpName.Contains(TEXT("xform")))
{
// Not a transform node, so not a Heightfield
// We just need to destroy the landscape asset node
return FHoudiniEngineUtils::DestroyHoudiniAsset( ConnectedAssetId );
}
// The landscape was marshalled as a heightfield, so we need to destroy and disconnect
// the volvis nodes, all the merge node's input (each merge input is a volume for one
// of the layer/mask of the landscape )
// Query the volvis node id
// The volvis node is the fist input of the xform node
HAPI_NodeId VolvisNodeId = -1;
FHoudiniApi::QueryNodeInput(
FHoudiniEngine::Get().GetSession(),
ConnectedAssetId, 0, &VolvisNodeId );
// First, destroy the merge node and its inputs
// The merge node is in the first input of the volvis node
HAPI_NodeId MergeNodeId = -1;
FHoudiniApi::QueryNodeInput(
FHoudiniEngine::Get().GetSession(),
VolvisNodeId, 0, &MergeNodeId );
if ( MergeNodeId != -1 )
{
// Get the merge node info
HAPI_NodeInfo NodeInfo;
FHoudiniApi::NodeInfo_Init(&NodeInfo);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo(
FHoudiniEngine::Get().GetSession(), MergeNodeId, &NodeInfo ), false );
for ( int32 n = 0; n < NodeInfo.inputCount; n++ )
{
// Get the Input node ID from the host ID
HAPI_NodeId InputNodeId = -1;
if ( HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput(
FHoudiniEngine::Get().GetSession(),
MergeNodeId, n, &InputNodeId ) )
break;
if ( InputNodeId == -1 )
break;
// Disconnect and Destroy that input
FHoudiniEngineUtils::HapiDisconnectAsset( MergeNodeId, n );
FHoudiniEngineUtils::DestroyHoudiniAsset( InputNodeId );
}
}
// Second step, destroy all the volumes GEO assets
for ( HAPI_NodeId AssetNodeId : CreatedInputAssetIds )
{
FHoudiniEngineUtils::DestroyHoudiniAsset( AssetNodeId );
}
CreatedInputAssetIds.Empty();
// Finally disconnect and destroy the xform, volvis and merge nodes, then destroy them
FHoudiniEngineUtils::HapiDisconnectAsset( ConnectedAssetId, 0 );
FHoudiniEngineUtils::HapiDisconnectAsset( VolvisNodeId, 0 );
FHoudiniEngineUtils::DestroyHoudiniAsset( MergeNodeId );
FHoudiniEngineUtils::DestroyHoudiniAsset( VolvisNodeId );
return FHoudiniEngineUtils::DestroyHoudiniAsset( ConnectedAssetId );
}
FColor
FHoudiniLandscapeUtils::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
bool
FHoudiniLandscapeUtils::ExtractLandscapeData(
ALandscapeProxy * LandscapeProxy, TSet<ULandscapeComponent *>& SelectedComponents,
const bool& bExportLighting, const bool& bExportTileUVs, const bool& bExportNormalizedUVs,
TArray<FVector>& LandscapePositionArray,
TArray<FVector>& LandscapeNormalArray,
TArray<FVector>& LandscapeUVArray,
TArray<FIntPoint>& LandscapeComponentVertexIndicesArray,
TArray<const char *>& LandscapeComponentNameArray,
TArray<FLinearColor>& LandscapeLightmapValues )
{
if ( !LandscapeProxy )
return false;
if ( SelectedComponents.Num() < 1 )
return false;
// Get runtime settings.
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
float GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION;
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
if ( HoudiniRuntimeSettings )
{
GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor;
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
}
// Calc all the needed sizes
int32 ComponentSizeQuads = ( ( LandscapeProxy->ComponentSizeQuads + 1 ) >> LandscapeProxy->ExportLOD ) - 1;
float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads;
int32 NumComponents = SelectedComponents.Num();
bool bExportOnlySelected = NumComponents != LandscapeProxy->LandscapeComponents.Num();
int32 VertexCountPerComponent = FMath::Square( ComponentSizeQuads + 1 );
int32 VertexCount = NumComponents * VertexCountPerComponent;
if ( !VertexCount )
return false;
// Initialize the data arrays
LandscapePositionArray.SetNumUninitialized( VertexCount );
LandscapeNormalArray.SetNumUninitialized( VertexCount );
LandscapeUVArray.SetNumUninitialized( VertexCount );
LandscapeComponentNameArray.SetNumUninitialized( VertexCount );
LandscapeComponentVertexIndicesArray.SetNumUninitialized( VertexCount );
if ( bExportLighting )
LandscapeLightmapValues.SetNumUninitialized( VertexCount );
//-----------------------------------------------------------------------------------------------------------------
// EXTRACT THE LANDSCAPE DATA
//-----------------------------------------------------------------------------------------------------------------
FIntPoint IntPointMax = FIntPoint::ZeroValue;
int32 AllPositionsIdx = 0;
for ( int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++ )
{
ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ ComponentIdx ];
if ( bExportOnlySelected && !SelectedComponents.Contains( LandscapeComponent ) )
continue;
TArray64< uint8 > LightmapMipData;
int32 LightmapMipSizeX = 0;
int32 LightmapMipSizeY = 0;
// See if we need to export lighting information.
if ( bExportLighting )
{
const FMeshMapBuildData* MapBuildData = LandscapeComponent->GetMeshMapBuildData();
FLightMap2D* LightMap2D = MapBuildData && MapBuildData->LightMap ? MapBuildData->LightMap->GetLightMap2D() : nullptr;
if ( LightMap2D && LightMap2D->IsValid( 0 ) )
{
UTexture2D * TextureLightmap = LightMap2D->GetTexture( 0 );
if ( TextureLightmap )
{
if ( TextureLightmap->Source.GetMipData(LightmapMipData, 0, 0, 0, nullptr))
{
LightmapMipSizeX = TextureLightmap->Source.GetSizeX();
LightmapMipSizeY = TextureLightmap->Source.GetSizeY();
}
else
{
LightmapMipData.Empty();
}
}
}
}
// Construct landscape component data interface to access raw data.
FLandscapeComponentDataInterface CDI( LandscapeComponent, LandscapeProxy->ExportLOD );
// Get name of this landscape component.
char * LandscapeComponentNameStr = FHoudiniEngineUtils::ExtractRawName( LandscapeComponent->GetName() );
for ( int32 VertexIdx = 0; VertexIdx < VertexCountPerComponent; VertexIdx++ )
{
int32 VertX = 0;
int32 VertY = 0;
CDI.VertexIndexToXY( VertexIdx, VertX, VertY );
// Get position.
FVector PositionVector = CDI.GetWorldVertex( VertX, VertY );
// Get normal / tangent / binormal.
FVector Normal = FVector::ZeroVector;
FVector TangentX = FVector::ZeroVector;
FVector TangentY = FVector::ZeroVector;
CDI.GetLocalTangentVectors( VertX, VertY, TangentX, TangentY, Normal );
// Export UVs.
FVector TextureUV = FVector::ZeroVector;
if ( bExportTileUVs )
{
// We want to export uvs per tile.
TextureUV = FVector( VertX, VertY, 0.0f );
// If we need to normalize UV space.
if ( bExportNormalizedUVs )
TextureUV /= ComponentSizeQuads;
}
else
{
// We want to export global uvs (default).
FIntPoint IntPoint = LandscapeComponent->GetSectionBase();
TextureUV = FVector( VertX * ScaleFactor + IntPoint.X, VertY * ScaleFactor + IntPoint.Y, 0.0f );
// Keep track of max offset.
IntPointMax = IntPointMax.ComponentMax( IntPoint );
}
if ( bExportLighting )
{
FLinearColor VertexLightmapColor( 0.0f, 0.0f, 0.0f, 1.0f );
if ( LightmapMipData.Num() > 0 )
{
FVector2D UVCoord( VertX, VertY );
UVCoord /= ( ComponentSizeQuads + 1 );
FColor LightmapColorRaw = PickVertexColorFromTextureMip(
LightmapMipData.GetData(), UVCoord, LightmapMipSizeX, LightmapMipSizeY );
VertexLightmapColor = LightmapColorRaw.ReinterpretAsLinear();
}
LandscapeLightmapValues[ AllPositionsIdx ] = VertexLightmapColor;
}
// Retrieve component transform.
const FTransform & ComponentTransform = LandscapeComponent->GetComponentTransform();
// Retrieve component scale.
const FVector & ScaleVector = ComponentTransform.GetScale3D();
// Perform normalization.
Normal /= ScaleVector;
Normal.Normalize();
TangentX /= ScaleVector;
TangentX.Normalize();
TangentY /= ScaleVector;
TangentY.Normalize();
// Perform position scaling.
FVector PositionTransformed = PositionVector / GeneratedGeometryScaleFactor;
if ( ImportAxis == HRSAI_Unreal )
{
LandscapePositionArray[ AllPositionsIdx ].X = PositionTransformed.X;
LandscapePositionArray[ AllPositionsIdx ].Y = PositionTransformed.Z;
LandscapePositionArray[ AllPositionsIdx ].Z = PositionTransformed.Y;
Swap( Normal.Y, Normal.Z );
}
else if ( ImportAxis == HRSAI_Houdini )
{
LandscapePositionArray[ AllPositionsIdx ].X = PositionTransformed.X;
LandscapePositionArray[ AllPositionsIdx ].Y = PositionTransformed.Y;
LandscapePositionArray[ AllPositionsIdx ].Z = PositionTransformed.Z;
}
else
{
// Not a valid enum value.
check( 0 );
}
// Store landscape component name for this point.
LandscapeComponentNameArray[ AllPositionsIdx ] = LandscapeComponentNameStr;
// Store vertex index (x,y) for this point.
LandscapeComponentVertexIndicesArray[ AllPositionsIdx ].X = VertX;
LandscapeComponentVertexIndicesArray[ AllPositionsIdx ].Y = VertY;
// Store point normal.
LandscapeNormalArray[ AllPositionsIdx ] = Normal;
// Store uv.
LandscapeUVArray[ AllPositionsIdx ] = TextureUV;
AllPositionsIdx++;
}
}
// If we need to normalize UV space and we are doing global UVs.
if ( !bExportTileUVs && bExportNormalizedUVs )
{
IntPointMax += FIntPoint( ComponentSizeQuads, ComponentSizeQuads );
IntPointMax = IntPointMax.ComponentMax( FIntPoint( 1, 1 ) );
for ( int32 UVIdx = 0; UVIdx < VertexCount; ++UVIdx )
{
FVector & PositionUV = LandscapeUVArray[ UVIdx ];
PositionUV.X /= IntPointMax.X;
PositionUV.Y /= IntPointMax.Y;
}
}
return true;
}
#endif
bool FHoudiniLandscapeUtils::AddLandscapePositionAttribute( const HAPI_NodeId& NodeId, const TArray< FVector >& LandscapePositionArray )
{
int32 VertexCount = LandscapePositionArray.Num();
if ( VertexCount < 3 )
return false;
// Create point attribute info containing positions.
HAPI_AttributeInfo AttributeInfoPointPosition;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointPosition);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointPosition );
AttributeInfoPointPosition.count = VertexCount;
AttributeInfoPointPosition.tupleSize = 3;
AttributeInfoPointPosition.exists = true;
AttributeInfoPointPosition.owner = HAPI_ATTROWNER_POINT;
AttributeInfoPointPosition.storage = HAPI_STORAGETYPE_FLOAT;
AttributeInfoPointPosition.originalOwner = HAPI_ATTROWNER_INVALID;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), NodeId, 0,
HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(), NodeId, 0,
HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition,
(const float *)LandscapePositionArray.GetData(),
0, AttributeInfoPointPosition.count ), false );
return true;
}
bool FHoudiniLandscapeUtils::AddLandscapeNormalAttribute( const HAPI_NodeId& NodeId, const TArray<FVector>& LandscapeNormalArray )
{
int32 VertexCount = LandscapeNormalArray.Num();
if ( VertexCount < 3 )
return false;
HAPI_AttributeInfo AttributeInfoPointNormal;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointNormal);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointNormal );
AttributeInfoPointNormal.count = VertexCount;
AttributeInfoPointNormal.tupleSize = 3;
AttributeInfoPointNormal.exists = true;
AttributeInfoPointNormal.owner = HAPI_ATTROWNER_POINT;
AttributeInfoPointNormal.storage = HAPI_STORAGETYPE_FLOAT;
AttributeInfoPointNormal.originalOwner = HAPI_ATTROWNER_INVALID;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), NodeId,
0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal,
(const float *)LandscapeNormalArray.GetData(), 0, AttributeInfoPointNormal.count ), false );
return true;
}
bool FHoudiniLandscapeUtils::AddLandscapeUVAttribute( const HAPI_NodeId& NodeId, const TArray<FVector>& LandscapeUVArray )
{
int32 VertexCount = LandscapeUVArray.Num();
if ( VertexCount < 3 )
return false;
HAPI_AttributeInfo AttributeInfoPointUV;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointUV);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointUV );
AttributeInfoPointUV.count = VertexCount;
AttributeInfoPointUV.tupleSize = 3;
AttributeInfoPointUV.exists = true;
AttributeInfoPointUV.owner = HAPI_ATTROWNER_POINT;
AttributeInfoPointUV.storage = HAPI_STORAGETYPE_FLOAT;
AttributeInfoPointUV.originalOwner = HAPI_ATTROWNER_INVALID;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), NodeId,
0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
NodeId, 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV,
(const float *)LandscapeUVArray.GetData(), 0, AttributeInfoPointUV.count ), false );
return true;
}
bool FHoudiniLandscapeUtils::AddLandscapeComponentVertexIndicesAttribute( const HAPI_NodeId& NodeId, const TArray<FIntPoint>& LandscapeComponentVertexIndicesArray )
{
int32 VertexCount = LandscapeComponentVertexIndicesArray.Num();
if ( VertexCount < 3 )
return false;
HAPI_AttributeInfo AttributeInfoPointLandscapeComponentVertexIndices;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentVertexIndices);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentVertexIndices );
AttributeInfoPointLandscapeComponentVertexIndices.count = VertexCount;
AttributeInfoPointLandscapeComponentVertexIndices.tupleSize = 2;
AttributeInfoPointLandscapeComponentVertexIndices.exists = true;
AttributeInfoPointLandscapeComponentVertexIndices.owner = HAPI_ATTROWNER_POINT;
AttributeInfoPointLandscapeComponentVertexIndices.storage = HAPI_STORAGETYPE_INT;
AttributeInfoPointLandscapeComponentVertexIndices.originalOwner = HAPI_ATTROWNER_INVALID;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), NodeId,
0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX,
&AttributeInfoPointLandscapeComponentVertexIndices ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeIntData(
FHoudiniEngine::Get().GetSession(),
NodeId, 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX,
&AttributeInfoPointLandscapeComponentVertexIndices,
(const int * )LandscapeComponentVertexIndicesArray.GetData(), 0,
AttributeInfoPointLandscapeComponentVertexIndices.count ), false );
return true;
}
bool FHoudiniLandscapeUtils::AddLandscapeComponentNameAttribute( const HAPI_NodeId& NodeId, const TArray<const char *>& LandscapeComponentNameArray )
{
int32 VertexCount = LandscapeComponentNameArray.Num();
if ( VertexCount < 3 )
return false;
// Create point attribute containing landscape component name.
HAPI_AttributeInfo AttributeInfoPointLandscapeComponentNames;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentNames);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentNames );
AttributeInfoPointLandscapeComponentNames.count = VertexCount;
AttributeInfoPointLandscapeComponentNames.tupleSize = 1;
AttributeInfoPointLandscapeComponentNames.exists = true;
AttributeInfoPointLandscapeComponentNames.owner = HAPI_ATTROWNER_POINT;
AttributeInfoPointLandscapeComponentNames.storage = HAPI_STORAGETYPE_STRING;
AttributeInfoPointLandscapeComponentNames.originalOwner = HAPI_ATTROWNER_INVALID;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), NodeId, 0,
HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME,
&AttributeInfoPointLandscapeComponentNames), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(), NodeId, 0,
HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME,
&AttributeInfoPointLandscapeComponentNames,
(const char **)LandscapeComponentNameArray.GetData(),
0, AttributeInfoPointLandscapeComponentNames.count ), false );
return true;
}
bool FHoudiniLandscapeUtils::AddLandscapeLightmapColorAttribute( const HAPI_NodeId& NodeId, const TArray<FLinearColor>& LandscapeLightmapValues )
{
int32 VertexCount = LandscapeLightmapValues.Num();
HAPI_AttributeInfo AttributeInfoPointLightmapColor;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLightmapColor);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLightmapColor );
AttributeInfoPointLightmapColor.count = VertexCount;
AttributeInfoPointLightmapColor.tupleSize = 4;
AttributeInfoPointLightmapColor.exists = true;
AttributeInfoPointLightmapColor.owner = HAPI_ATTROWNER_POINT;
AttributeInfoPointLightmapColor.storage = HAPI_STORAGETYPE_FLOAT;
AttributeInfoPointLightmapColor.originalOwner = HAPI_ATTROWNER_INVALID;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), NodeId,
0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeFloatData(
FHoudiniEngine::Get().GetSession(),
NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor,
(const float *)LandscapeLightmapValues.GetData(), 0,
AttributeInfoPointLightmapColor.count ), false );
return true;
}
bool FHoudiniLandscapeUtils::AddLandscapeMeshIndicesAndMaterialsAttribute(
const HAPI_NodeId& NodeId, const bool& bExportMaterials,
const int32& ComponentSizeQuads, const int32& QuadCount,
ALandscapeProxy * LandscapeProxy,
const TSet< ULandscapeComponent * >& SelectedComponents )
{
if ( !LandscapeProxy )
return false;
// Compute number of necessary indices.
int32 IndexCount = QuadCount * 4;
if ( IndexCount < 0 )
return false;
int32 VertexCountPerComponent = FMath::Square( ComponentSizeQuads + 1 );
// Get runtime settings.
EHoudiniRuntimeSettingsAxisImport ImportAxis = HRSAI_Unreal;
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
if ( HoudiniRuntimeSettings )
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
// Array holding indices data.
TArray< int32 > LandscapeIndices;
LandscapeIndices.SetNumUninitialized( IndexCount );
// Allocate space for face names.
// The LandscapeMaterial and HoleMaterial per point
TArray< const char * > FaceMaterials;
TArray< const char * > FaceHoleMaterials;
FaceMaterials.SetNumUninitialized( QuadCount );
FaceHoleMaterials.SetNumUninitialized( QuadCount );
int32 VertIdx = 0;
int32 QuadIdx = 0;
char * MaterialRawStr = nullptr;
char * MaterialHoleRawStr = nullptr;
const int32 QuadComponentCount = ComponentSizeQuads + 1;
for ( int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++ )
{
ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ ComponentIdx ];
if ( !SelectedComponents.Contains( LandscapeComponent ) )
continue;
if ( bExportMaterials )
{
// If component has an override material, we need to get the raw name (if exporting materials).
if ( LandscapeComponent->OverrideMaterial )
{
MaterialRawStr = FHoudiniEngineUtils::ExtractRawName(LandscapeComponent->OverrideMaterial->GetName());
}
// If component has an override hole material, we need to get the raw name (if exporting materials).
if ( LandscapeComponent->OverrideHoleMaterial )
{
MaterialHoleRawStr = FHoudiniEngineUtils::ExtractRawName(LandscapeComponent->OverrideHoleMaterial->GetName());
}
}
int32 BaseVertIndex = ComponentIdx * VertexCountPerComponent;
for ( int32 YIdx = 0; YIdx < ComponentSizeQuads; YIdx++ )
{
for ( int32 XIdx = 0; XIdx < ComponentSizeQuads; XIdx++ )
{
if ( ImportAxis == HRSAI_Unreal )
{
LandscapeIndices[ VertIdx + 0 ] = BaseVertIndex + ( XIdx + 0 ) + ( YIdx + 0 ) * QuadComponentCount;
LandscapeIndices[ VertIdx + 1 ] = BaseVertIndex + ( XIdx + 1 ) + ( YIdx + 0 ) * QuadComponentCount;
LandscapeIndices[ VertIdx + 2 ] = BaseVertIndex + ( XIdx + 1 ) + ( YIdx + 1 ) * QuadComponentCount;
LandscapeIndices[ VertIdx + 3 ] = BaseVertIndex + ( XIdx + 0 ) + ( YIdx + 1 ) * QuadComponentCount;
}
else if (ImportAxis == HRSAI_Houdini)
{
LandscapeIndices[ VertIdx + 0 ] = BaseVertIndex + ( XIdx + 0 ) + ( YIdx + 0 ) * QuadComponentCount;
LandscapeIndices[ VertIdx + 1 ] = BaseVertIndex + ( XIdx + 0 ) + ( YIdx + 1 ) * QuadComponentCount;
LandscapeIndices[ VertIdx + 2 ] = BaseVertIndex + ( XIdx + 1 ) + ( YIdx + 1 ) * QuadComponentCount;
LandscapeIndices[ VertIdx + 3 ] = BaseVertIndex + ( XIdx + 1 ) + ( YIdx + 0 ) * QuadComponentCount;
}
else
{
// Not a valid enum value.
check( 0 );
}
// Store override materials (if exporting materials).
if ( bExportMaterials )
{
FaceMaterials[ QuadIdx ] = MaterialRawStr;
FaceHoleMaterials[ QuadIdx ] = MaterialHoleRawStr;
}
VertIdx += 4;
QuadIdx++;
}
}
}
// We can now set vertex list.
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetVertexList(
FHoudiniEngine::Get().GetSession(), NodeId,
0, LandscapeIndices.GetData(), 0, LandscapeIndices.Num() ), false );
// We need to generate array of face counts.
TArray< int32 > LandscapeFaces;
LandscapeFaces.Init( 4, QuadCount );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetFaceCounts(
FHoudiniEngine::Get().GetSession(), NodeId,
0, LandscapeFaces.GetData(), 0, LandscapeFaces.Num() ), false );
if ( bExportMaterials )
{
if ( !FaceMaterials.Contains( nullptr ) )
{
// Get name of attribute used for marshalling materials.
std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL;
if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeMaterial.IsEmpty() )
{
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeMaterial,
MarshallingAttributeMaterialName );
}
// Marshall in override primitive material names.
HAPI_AttributeInfo AttributeInfoPrimitiveMaterial;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterial);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterial );
AttributeInfoPrimitiveMaterial.count = FaceMaterials.Num();
AttributeInfoPrimitiveMaterial.tupleSize = 1;
AttributeInfoPrimitiveMaterial.exists = true;
AttributeInfoPrimitiveMaterial.owner = HAPI_ATTROWNER_PRIM;
AttributeInfoPrimitiveMaterial.storage = HAPI_STORAGETYPE_STRING;
AttributeInfoPrimitiveMaterial.originalOwner = HAPI_ATTROWNER_INVALID;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), NodeId, 0,
MarshallingAttributeMaterialName.c_str(), &AttributeInfoPrimitiveMaterial ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(), NodeId, 0,
MarshallingAttributeMaterialName.c_str(), &AttributeInfoPrimitiveMaterial,
(const char **)FaceMaterials.GetData(), 0, AttributeInfoPrimitiveMaterial.count ), false );
}
if ( !FaceHoleMaterials.Contains( nullptr ) )
{
// Get name of attribute used for marshalling hole materials.
std::string MarshallingAttributeMaterialHoleName = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE;
if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeMaterialHole.IsEmpty() )
{
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeMaterialHole,
MarshallingAttributeMaterialHoleName );
}
// Marshall in override primitive material hole names.
HAPI_AttributeInfo AttributeInfoPrimitiveMaterialHole;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterialHole);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterialHole );
AttributeInfoPrimitiveMaterialHole.count = FaceHoleMaterials.Num();
AttributeInfoPrimitiveMaterialHole.tupleSize = 1;
AttributeInfoPrimitiveMaterialHole.exists = true;
AttributeInfoPrimitiveMaterialHole.owner = HAPI_ATTROWNER_PRIM;
AttributeInfoPrimitiveMaterialHole.storage = HAPI_STORAGETYPE_STRING;
AttributeInfoPrimitiveMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(),
NodeId, 0, MarshallingAttributeMaterialHoleName.c_str(),
&AttributeInfoPrimitiveMaterialHole ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(),
NodeId, 0, MarshallingAttributeMaterialHoleName.c_str(),
&AttributeInfoPrimitiveMaterialHole, (const char **) FaceHoleMaterials.GetData(), 0,
AttributeInfoPrimitiveMaterialHole.count ), false );
}
}
return true;
}
bool FHoudiniLandscapeUtils::AddLandscapeTileAttribute(
const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const int32& TileIdx )
{
HAPI_AttributeInfo AttributeInfoTileIndex;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoTileIndex);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoTileIndex );
AttributeInfoTileIndex.count = 1;
AttributeInfoTileIndex.tupleSize = 1;
AttributeInfoTileIndex.exists = true;
AttributeInfoTileIndex.owner = HAPI_ATTROWNER_PRIM;
AttributeInfoTileIndex.storage = HAPI_STORAGETYPE_INT;
AttributeInfoTileIndex.originalOwner = HAPI_ATTROWNER_INVALID;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), NodeId,
PartId, "tile", &AttributeInfoTileIndex ), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeIntData(
FHoudiniEngine::Get().GetSession(),
NodeId, PartId, "tile", &AttributeInfoTileIndex,
(const int *)&TileIdx, 0, AttributeInfoTileIndex.count ), false );
return true;
}
// Add the landscape component extent attribute
bool FHoudiniLandscapeUtils::AddLandscapeComponentExtentAttributes(
const HAPI_NodeId& NodeId, const HAPI_PartId& PartId,
const int32& MinX, const int32& MaxX,
const int32& MinY, const int32& MaxY )
{
// Create an AttributeInfo
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_PRIM;
AttributeInfo.storage = HAPI_STORAGETYPE_INT;
AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID;
// Add the landscape_component_min_X primitive attribute
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(),
NodeId, PartId, "landscape_component_min_X", &AttributeInfo ), false);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData(
FHoudiniEngine::Get().GetSession(),
NodeId, PartId, "landscape_component_min_X", &AttributeInfo,
(const int *)&MinX, 0, AttributeInfo.count), false);
// Add the landscape_component_max_X primitive attribute
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(),
NodeId, PartId, "landscape_component_max_X", &AttributeInfo), false);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData(
FHoudiniEngine::Get().GetSession(),
NodeId, PartId, "landscape_component_max_X", &AttributeInfo,
(const int *)&MaxX, 0, AttributeInfo.count), false);
// Add the landscape_component_min_Y primitive attribute
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(),
NodeId, PartId, "landscape_component_min_Y", &AttributeInfo), false);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData(
FHoudiniEngine::Get().GetSession(),
NodeId, PartId, "landscape_component_min_Y", &AttributeInfo,
(const int *)&MinY, 0, AttributeInfo.count), false);
// Add the landscape_component_max_Y primitive attribute
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(),
NodeId, PartId, "landscape_component_max_Y", &AttributeInfo), false);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData(
FHoudiniEngine::Get().GetSession(),
NodeId, PartId, "landscape_component_max_Y", &AttributeInfo,
(const int *)&MaxY, 0, AttributeInfo.count), false);
return true;
}
// Read the landscape component extent attribute from a heightfield
bool FHoudiniLandscapeUtils::GetLandscapeComponentExtentAttributes(
const FHoudiniGeoPartObject& HoudiniGeoPartObject,
int32& MinX, int32& MaxX,
int32& MinY, int32& MaxY )
{
// If we dont have minX, we likely dont have the others too
if ( !FHoudiniEngineUtils::HapiCheckAttributeExists(
HoudiniGeoPartObject, "landscape_component_min_X", HAPI_ATTROWNER_PRIM) )
return false;
// Create an AttributeInfo
HAPI_AttributeInfo AttributeInfo;
FHoudiniApi::AttributeInfo_Init(&AttributeInfo);
//FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfo);
// Get MinX
TArray<int32> IntData;
if ( !FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(
HoudiniGeoPartObject, "landscape_component_min_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM ) )
return false;
if ( IntData.Num() > 0 )
MinX = IntData[0];
// Get MaxX
if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(
HoudiniGeoPartObject, "landscape_component_max_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM))
return false;
if (IntData.Num() > 0)
MaxX = IntData[0];
// Get MinY
if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(
HoudiniGeoPartObject, "landscape_component_min_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM))
return false;
if (IntData.Num() > 0)
MinY = IntData[0];
// Get MaxX
if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(
HoudiniGeoPartObject, "landscape_component_max_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM))
return false;
if (IntData.Num() > 0)
MaxY = IntData[0];
return true;
}
#if WITH_EDITOR
bool
FHoudiniLandscapeUtils::CreateAllLandscapes(
FHoudiniCookParams& HoudiniCookParams,
const TArray< FHoudiniGeoPartObject > & FoundVolumes,
TMap< FHoudiniGeoPartObject, TWeakObjectPtr<ALandscapeProxy> >& Landscapes,
TMap< FHoudiniGeoPartObject, TWeakObjectPtr<ALandscapeProxy> >& NewLandscapes,
TArray<ALandscapeProxy *>& InputLandscapeToUpdate,
float ForcedZMin , float ForcedZMax )
{
// Get runtime settings.
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
if ( HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesForceMinMaxValues )
{
ForcedZMin = HoudiniRuntimeSettings->MarshallingLandscapesForcedMinValue;
ForcedZMax = HoudiniRuntimeSettings->MarshallingLandscapesForcedMaxValue;
}
// First, we need to extract proper height data from FoundVolumes
TArray< const FHoudiniGeoPartObject* > FoundHeightfields;
FHoudiniLandscapeUtils::GetHeightfieldsInArray( FoundVolumes, FoundHeightfields );
// If we have multiple heightfields, we want to convert them using the same Z range
// Either that range has been specified/forced by the user, or we'll have to calculate it from all the height volumes.
float fGlobalMin = ForcedZMin, fGlobalMax = ForcedZMax;
if ( FoundHeightfields.Num() > 1 && ( fGlobalMin == 0.0f && fGlobalMax == 0.0f ) )
FHoudiniLandscapeUtils::CalcHeightfieldsArrayGlobalZMinZMax( FoundHeightfields, fGlobalMin, fGlobalMax );
// We want to be doing a similar things for the mask/layer volumes
TMap<FString, float> GlobalMinimums;
TMap<FString, float> GlobalMaximums;
if ( FoundVolumes.Num() > 1 )
FHoudiniLandscapeUtils::CalcHeightfieldsArrayGlobalZMinZMax( FoundVolumes, GlobalMinimums, GlobalMaximums );
// Try to create a Landscape for each HeightData found
NewLandscapes.Empty();
for (TArray< const FHoudiniGeoPartObject* >::TConstIterator IterHeighfields(FoundHeightfields); IterHeighfields; ++IterHeighfields)
{
// Get the current Heightfield GeoPartObject
const FHoudiniGeoPartObject* CurrentHeightfield = *IterHeighfields;
if (!CurrentHeightfield)
continue;
// Look for the unreal_landscape_streaming_proxy attribute on the height volume
bool bCreateLandscapeStreamingProxy = false;
TArray<int32> IntData;
HAPI_AttributeInfo AttributeInfo;
FHoudiniApi::AttributeInfo_Init(&AttributeInfo);
//FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfo);
if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(
CurrentHeightfield->AssetId, CurrentHeightfield->ObjectId,
CurrentHeightfield->GeoId, CurrentHeightfield->PartId,
"unreal_landscape_streaming_proxy", AttributeInfo, IntData, 1))
{
if (IntData.Num() > 0 && IntData[0] != 0)
bCreateLandscapeStreamingProxy = true;
}
// Try to find the landscape previously created from that HGPO
ALandscapeProxy * FoundLandscape = nullptr;
if (TWeakObjectPtr<ALandscapeProxy>* FoundLandscapePtr = Landscapes.Find(*CurrentHeightfield))
{
FoundLandscape = FoundLandscapePtr->Get();
if (!FoundLandscape || !FoundLandscape->IsValidLowLevel())
FoundLandscape = nullptr;
}
bool bLandscapeNeedsToBeUpdated = true;
if (!CurrentHeightfield->bHasGeoChanged)
{
// The Geo has not changed, do we need to recreate the landscape?
if (FoundLandscape)
{
// Check that all layers/mask have not changed too
TArray< const FHoudiniGeoPartObject* > FoundLayers;
FHoudiniLandscapeUtils::GetHeightfieldsLayersInArray(FoundVolumes, *CurrentHeightfield, FoundLayers);
bool bLayersHaveChanged = false;
for (int32 n = 0; n < FoundLayers.Num(); n++)
{
if (FoundLayers[n] && FoundLayers[n]->bHasGeoChanged)
{
bLayersHaveChanged = true;
break;
}
}
if (!bLayersHaveChanged)
{
// Height and layers/masks have not changed, there is no need to reimport the landscape
bLandscapeNeedsToBeUpdated = false;
}
// Force update/recreation if the actor is not of the desired type
if ( (!FoundLandscape->IsA(ALandscapeStreamingProxy::StaticClass()) && bCreateLandscapeStreamingProxy)
|| (FoundLandscape->IsA(ALandscapeStreamingProxy::StaticClass()) && !bCreateLandscapeStreamingProxy) )
bLandscapeNeedsToBeUpdated = true;
}
}
// Height and mask volumes have not changed
// No need to update the Landscape
if (!bLandscapeNeedsToBeUpdated)
{
// We can add the landscape to the map and remove it from the old one to avoid its destruction
NewLandscapes.Add(*CurrentHeightfield, FoundLandscape);
Landscapes.Remove(*CurrentHeightfield);
continue;
}
HAPI_NodeId HeightFieldNodeId = CurrentHeightfield->HapiGeoGetNodeId();
// We need to see if the current heightfield has an unreal_material or unreal_hole_material assigned to it
UMaterialInterface* LandscapeMaterial = nullptr;
UMaterialInterface* LandscapeHoleMaterial = nullptr;
FHoudiniLandscapeUtils::GetHeightFieldLandscapeMaterials(*CurrentHeightfield, LandscapeMaterial, LandscapeHoleMaterial);
// Extract the Float Data from the Heightfield
TArray< float > FloatValues;
HAPI_VolumeInfo VolumeInfo;
float FloatMin, FloatMax;
if (!FHoudiniLandscapeUtils::GetHeightfieldData(*CurrentHeightfield, FloatValues, VolumeInfo, FloatMin, FloatMax))
continue;
// Do we need to convert the heightfields using the same global Min/Max
if (fGlobalMin != fGlobalMax)
{
FloatMin = fGlobalMin;
FloatMax = fGlobalMax;
}
// See if we need to create a new Landscape Actor for this heightfield:
// Either we haven't created one yet, or it's size has changed
int32 HoudiniXSize = VolumeInfo.yLength;
int32 HoudiniYSize = VolumeInfo.xLength;
int32 UnrealXSize = -1;
int32 UnrealYSize = -1;
int32 NumSectionPerLandscapeComponent = -1;
int32 NumQuadsPerLandscapeSection = -1;
if (!FHoudiniLandscapeUtils::CalcLandscapeSizeFromHeightfieldSize(
HoudiniXSize, HoudiniYSize,
UnrealXSize, UnrealYSize,
NumSectionPerLandscapeComponent,
NumQuadsPerLandscapeSection))
continue;
// See if the Heightfield has component extent attributes
// This would mean that we'd need to update parts of the landscape only
bool UpdateLandscapeComponent = false;
int32 MinX, MaxX, MinY, MaxY;
bool HasComponentExtent = GetLandscapeComponentExtentAttributes(
*CurrentHeightfield, MinX, MaxX, MinY, MaxY);
// Try to see if we have an input landscape that matches the size of the current HGPO
bool UpdatingInputLandscape = false;
for (int nIdx = 0; nIdx < InputLandscapeToUpdate.Num(); nIdx++)
{
ALandscapeProxy* CurrentInputLandscape = InputLandscapeToUpdate[nIdx];
if (!CurrentInputLandscape)
continue;
ULandscapeInfo* CurrentInfo = CurrentInputLandscape->GetLandscapeInfo();
if (!CurrentInfo)
continue;
int32 InputMinX = 0;
int32 InputMinY = 0;
int32 InputMaxX = 0;
int32 InputMaxY = 0;
CurrentInfo->GetLandscapeExtent(InputMinX, InputMinY, InputMaxX, InputMaxY);
// If the full size matches, we'll update that input landscape
bool SizeMatch = false;
if ((InputMaxX - InputMinX + 1) == UnrealXSize && (InputMaxY - InputMinY + 1) == UnrealYSize)
SizeMatch = true;
/*
// If not, see if we could update that landscape's components instead of the full landscape
if ( !SizeMatch && HasComponentExtent )
{
if ( (MinX >= InputMinX) && (MinX <= InputMaxX)
&& (MinY >= InputMinY) && (MinY <= InputMaxY)
&& (MaxX >= InputMinX) && (MaxX <= InputMaxX)
&& (MaxY >= InputMinY) && (MaxY <= InputMaxY) )
{
// Try to update this landscape component data
SizeMatch = true;
UpdateLandscapeComponent = true;
}
}
*/
// HF and landscape don't match, try another one
if ( !SizeMatch )
continue;
// Replace FoundLandscape by that input landscape
FoundLandscape = CurrentInputLandscape;
// If we're not updating part of that landscape via components,
// Remove that landscape from the input array so we dont try to update it twice
if ( !UpdateLandscapeComponent )
InputLandscapeToUpdate.RemoveAt(nIdx);
UpdatingInputLandscape = true;
}
// See if we need to create a new Landscape Actor for this heightfield
// Either we haven't created one yet, or it's size has changed
bool bLandscapeNeedsRecreate = true;
if ( UpdatingInputLandscape )
{
// No need to create a new landscape as we're modifying the input one
bLandscapeNeedsRecreate = false;
}
else if ( FoundLandscape && !FoundLandscape->IsPendingKill() )
{
// See if we can reuse the previous landscape
ULandscapeInfo* PreviousInfo = FoundLandscape->GetLandscapeInfo();
if ( PreviousInfo )
{
int32 PrevMinX = 0;
int32 PrevMinY = 0;
int32 PrevMaxX = 0;
int32 PrevMaxY = 0;
PreviousInfo->GetLandscapeExtent(PrevMinX, PrevMinY, PrevMaxX, PrevMaxY);
bool SizeMatch = false;
if ( (PrevMaxX - PrevMinX + 1) == UnrealXSize
&& (PrevMaxY - PrevMinY + 1) == UnrealYSize )
SizeMatch = true;
/*
// If not, see if we could update that landscape's component
if (!SizeMatch && HasComponentExtent)
{
if ((MinX >= PrevMinX) && (MinX <= PrevMaxX)
&& (MinY >= PrevMinY) && (MinY <= PrevMaxY)
&& (MaxX >= PrevMinX) && (MaxX <= PrevMaxX)
&& (MaxY >= PrevMinY) && (MaxY <= PrevMaxY))
{
// Try to update this landscape component data
SizeMatch = true;
UpdateLandscapeComponent = true;
}
}
*/
if ( SizeMatch )
{
// We can reuse the existing actor
bLandscapeNeedsRecreate = false;
}
}
}
if (!bLandscapeNeedsRecreate)
{
// Force update/recreation if the actor is not of the desired type
if ((!FoundLandscape->IsA(ALandscapeStreamingProxy::StaticClass()) && bCreateLandscapeStreamingProxy)
|| (FoundLandscape->IsA(ALandscapeStreamingProxy::StaticClass()) && !bCreateLandscapeStreamingProxy))
bLandscapeNeedsRecreate = true;
}
if ( bLandscapeNeedsRecreate )
{
// We need to create a new Landscape Actor
// Either we didnt create one before, or we cannot reuse the old one (size has changed)
// Convert the height data from Houdini's heightfield to Unreal's Landscape
TArray< uint16 > IntHeightData;
FTransform LandscapeTransform;
if (!FHoudiniLandscapeUtils::ConvertHeightfieldDataToLandscapeData(
FloatValues, VolumeInfo,
UnrealXSize, UnrealYSize,
FloatMin, FloatMax,
IntHeightData, LandscapeTransform))
continue;
// Look for all the layers/masks corresponding to the current heightfield
TArray< const FHoudiniGeoPartObject* > FoundLayers;
FHoudiniLandscapeUtils::GetHeightfieldsLayersInArray(FoundVolumes, *CurrentHeightfield, FoundLayers);
// Extract and convert the Landscape layers
TArray< FLandscapeImportLayerInfo > ImportLayerInfos;
if (!FHoudiniLandscapeUtils::CreateLandscapeLayers(HoudiniCookParams, FoundLayers, *CurrentHeightfield,
UnrealXSize, UnrealYSize, GlobalMinimums, GlobalMaximums, ImportLayerInfos))
continue;
// Create the actual Landscape
ALandscapeProxy * CurrentLandscape = CreateLandscape(
IntHeightData, ImportLayerInfos,
LandscapeTransform, UnrealXSize, UnrealYSize,
NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection,
LandscapeMaterial, LandscapeHoleMaterial, bCreateLandscapeStreamingProxy );
if (!CurrentLandscape)
continue;
// Update the visibility mask / layer if we have any
for (auto CurrLayerInfo : ImportLayerInfos)
{
if (CurrLayerInfo.LayerInfo && CurrLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase))
{
CurrentLandscape->VisibilityLayer = CurrLayerInfo.LayerInfo;
CurrentLandscape->VisibilityLayer->bNoWeightBlend = true;
CurrentLandscape->VisibilityLayer->AddToRoot();
}
}
// Add the new landscape to the map
NewLandscapes.Add(*CurrentHeightfield, CurrentLandscape);
}
else
{
// We're reusing an existing landscape Actor
// Only update the height / layer data that has changed
// Update the materials if they have changed
if ( FoundLandscape->GetLandscapeMaterial() != LandscapeMaterial )
FoundLandscape->LandscapeMaterial = LandscapeMaterial;
if ( FoundLandscape->GetLandscapeHoleMaterial() != LandscapeHoleMaterial )
FoundLandscape->LandscapeHoleMaterial = LandscapeHoleMaterial;
// Update the previous landscape's height data
// Decide if we need to create a new Landscape or if we can reuse the previous one
ULandscapeInfo* PreviousInfo = FoundLandscape->GetLandscapeInfo();
if ( !PreviousInfo )
continue;
FLandscapeEditDataInterface LandscapeEdit(PreviousInfo);
// Update the height data only if it's marked as changed
if ( CurrentHeightfield->bHasGeoChanged )
{
// Convert the height data from Houdini's heightfield to Unreal's Landscape
TArray< uint16 > IntHeightData;
FTransform LandscapeTransform;
if ( !FHoudiniLandscapeUtils::ConvertHeightfieldDataToLandscapeData(
FloatValues, VolumeInfo,
UnrealXSize, UnrealYSize,
FloatMin, FloatMax,
IntHeightData, LandscapeTransform,
UpdateLandscapeComponent ) )
continue;
if ( !UpdateLandscapeComponent )
LandscapeEdit.SetHeightData(0, 0, UnrealXSize - 1, UnrealYSize - 1, IntHeightData.GetData(), 0, true);
else
LandscapeEdit.SetHeightData(MinX, MinY, MaxX, MaxY, IntHeightData.GetData(), 0, true);
// Set the landscape Transform
if ( !UpdateLandscapeComponent )
FoundLandscape->SetActorRelativeTransform(LandscapeTransform);
}
// Look for all the layers/masks corresponding to the current heightfield
TArray< const FHoudiniGeoPartObject* > FoundLayers;
FHoudiniLandscapeUtils::GetHeightfieldsLayersInArray(FoundVolumes, *CurrentHeightfield, FoundLayers);
// Get the names of all the non weight blended layers
TArray< FString > NonWeightBlendedLayerNames;
FHoudiniLandscapeUtils::GetNonWeightBlendedLayerNames(*CurrentHeightfield, NonWeightBlendedLayerNames);
for ( auto LayerGeoPartObject : FoundLayers )
{
if (!LayerGeoPartObject)
continue;
if (!LayerGeoPartObject->IsValid())
continue;
if (LayerGeoPartObject->AssetId == -1)
continue;
if ( !LayerGeoPartObject->bHasGeoChanged )
continue;
// Extract the layer's float data from the HF
TArray< float > FloatLayerData;
HAPI_VolumeInfo LayerVolumeInfo;
FHoudiniApi::VolumeInfo_Init(&LayerVolumeInfo);
float LayerMin = 0;
float LayerMax = 0;
if (!FHoudiniLandscapeUtils::GetHeightfieldData(*LayerGeoPartObject, FloatLayerData, LayerVolumeInfo, LayerMin, LayerMax))
continue;
// No need to create flat layers as Unreal will remove them afterwards..
if (LayerMin == LayerMax)
continue;
// Get the layer's name
FString LayerString;
FHoudiniEngineString(LayerVolumeInfo.nameSH).ToFString(LayerString);
// Check if that landscape layer has been marked as unit (range in [0-1]
if ( IsUnitLandscapeLayer( *LayerGeoPartObject ) )
{
LayerMin = 0.0f;
LayerMax = 1.0f;
}
else
{
// Do we want to convert the layer's value using the global Min/Max
if (GlobalMaximums.Contains(LayerString))
LayerMax = GlobalMaximums[LayerString];
if (GlobalMinimums.Contains(LayerString))
LayerMin = GlobalMinimums[LayerString];
}
// Find the ImportLayerInfo and LayerInfo objects
ObjectTools::SanitizeObjectName(LayerString);
FName LayerName(*LayerString);
FLandscapeImportLayerInfo currentLayerInfo(LayerName);
UPackage * Package = nullptr;
currentLayerInfo.LayerInfo = FHoudiniLandscapeUtils::CreateLandscapeLayerInfoObject(HoudiniCookParams, LayerString.GetCharArray().GetData(), Package, LayerGeoPartObject->GetPartId());
if (!currentLayerInfo.LayerInfo || !Package)
continue;
// Convert the float data to uint8
if ( !FHoudiniLandscapeUtils::ConvertHeightfieldLayerToLandscapeLayer(
FloatLayerData, LayerVolumeInfo.yLength, LayerVolumeInfo.xLength,
LayerMin, LayerMax,
UnrealXSize, UnrealYSize,
currentLayerInfo.LayerData,
UpdateLandscapeComponent ) )
continue;
// Store the data used to convert the Houdini float values to int in the DebugColor
// This is the only way we'll be able to reconvert those values back to their houdini equivalent afterwards...
// R = Min, G = Max, B = Spacing, A = ?
currentLayerInfo.LayerInfo->LayerUsageDebugColor.R = LayerMin;
currentLayerInfo.LayerInfo->LayerUsageDebugColor.G = LayerMax;
currentLayerInfo.LayerInfo->LayerUsageDebugColor.B = (LayerMax - LayerMin) / 255.0f;
currentLayerInfo.LayerInfo->LayerUsageDebugColor.A = PI;
// Updated non weight blended layers (visibility is by default non weight blended)
if (NonWeightBlendedLayerNames.Contains(LayerString) || LayerString.Equals(TEXT("visibility"), ESearchCase::IgnoreCase))
currentLayerInfo.LayerInfo->bNoWeightBlend = true;
else
currentLayerInfo.LayerInfo->bNoWeightBlend = false;
// Update the layer on the heightfield
if ( !UpdateLandscapeComponent )
LandscapeEdit.SetAlphaData( currentLayerInfo.LayerInfo, 0, 0, UnrealXSize - 1, UnrealYSize - 1, currentLayerInfo.LayerData.GetData(), 0 );
else
LandscapeEdit.SetAlphaData( currentLayerInfo.LayerInfo, MinX, MinY, MaxX, MaxY, currentLayerInfo.LayerData.GetData(), 0 );
if ( currentLayerInfo.LayerInfo && currentLayerInfo.LayerName.ToString().Equals( TEXT("Visibility"), ESearchCase::IgnoreCase ) )
{
FoundLandscape->VisibilityLayer = currentLayerInfo.LayerInfo;
FoundLandscape->VisibilityLayer->bNoWeightBlend = true;
FoundLandscape->VisibilityLayer->AddToRoot();
}
}
// Update the materials if they have changed
if (FoundLandscape->GetLandscapeMaterial() != LandscapeMaterial)
FoundLandscape->LandscapeMaterial = LandscapeMaterial;
if (FoundLandscape->GetLandscapeHoleMaterial() != LandscapeHoleMaterial)
FoundLandscape->LandscapeHoleMaterial = LandscapeHoleMaterial;
//FoundLandscape->RecreateCollisionComponents();
//PreviousInfo->UpdateLayerInfoMap();
//PreviousInfo->RecreateCollisionComponents();
//PreviousInfo->UpdateAllAddCollisions();
// We can add the landscape to the new map
NewLandscapes.Add(*CurrentHeightfield, FoundLandscape);
}
}
for (auto Iter : NewLandscapes)
{
FHoudiniGeoPartObject HGPO = Iter.Key;
TWeakObjectPtr<ALandscapeProxy> Landscape = Iter.Value;
if (!Landscape.IsValid())
continue;
// Update the landscape's collisions
Landscape->RecreateCollisionComponents();
// Handle the HF's tags
// See if we have unreal_tag_ attribute
TArray<FName> Tags;
if (!FHoudiniEngineUtils::GetUnrealTagAttributes(HGPO, Tags))
continue;
Landscape->Tags = Tags;
}
return true;
}
ALandscapeProxy *
FHoudiniLandscapeUtils::CreateLandscape(
const TArray< uint16 >& IntHeightData,
const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos,
const FTransform& LandscapeTransform,
const int32& XSize, const int32& YSize,
const int32& NumSectionPerLandscapeComponent, const int32& NumQuadsPerLandscapeSection,
UMaterialInterface* LandscapeMaterial, UMaterialInterface* LandscapeHoleMaterial,
const bool& CreateLandscapeStreamingProxy )
{
if ( ( XSize < 2 ) || ( YSize < 2 ) )
return nullptr;
if ( IntHeightData.Num() != ( XSize * YSize ) )
return nullptr;
if ( !GEditor )
return nullptr;
// Get the world we'll spawn the landscape in
UWorld* MyWorld = nullptr;
{
// We want to create the landscape in the landscape editor mode's world
FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext();
MyWorld = EditorWorldContext.World();
}
if ( !MyWorld )
return nullptr;
// We need to create the landscape now and assign it a new GUID so we can create the LayerInfos
ALandscapeProxy* LandscapeProxy = nullptr;
if ( CreateLandscapeStreamingProxy )
LandscapeProxy = MyWorld->SpawnActor<ALandscapeStreamingProxy>();
else
LandscapeProxy = MyWorld->SpawnActor<ALandscape>();
if (!LandscapeProxy)
return nullptr;
// Create a new GUID
FGuid currentGUID = FGuid::NewGuid();
LandscapeProxy->SetLandscapeGuid( currentGUID );
// Set the landscape Transform
LandscapeProxy->SetActorTransform( LandscapeTransform );
// Autosaving the layers prevents them for being deleted with the Asset
// Save the packages created for the LayerInfos
//if ( CreatedLayerInfoPackage.Num() > 0 )
// FEditorFileUtils::PromptForCheckoutAndSave( CreatedLayerInfoPackage, true, false );
// Import the landscape data
// Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue
LandscapeProxy->bCastStaticShadow = false;
if ( LandscapeMaterial )
LandscapeProxy->LandscapeMaterial = LandscapeMaterial;
if ( LandscapeHoleMaterial )
LandscapeProxy->LandscapeHoleMaterial = LandscapeHoleMaterial;
// Setting the layer type here.
ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive;
TMap<FGuid, TArray<uint16>> HeightmapDataPerLayers;
TMap<FGuid, TArray<FLandscapeImportLayerInfo>> MaterialLayerDataPerLayer;
HeightmapDataPerLayers.Add(FGuid(), IntHeightData);
MaterialLayerDataPerLayer.Add(FGuid(), ImportLayerInfos);
// Import the data
LandscapeProxy->Import(
currentGUID,
0, 0, XSize - 1, YSize - 1,
NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection,
HeightmapDataPerLayers, NULL,
MaterialLayerDataPerLayer, ImportLayerType );
// Copied straight from UE source code to avoid crash after importing the landscape:
// automatically calculate a lighting LOD that won't crash lightmass (hopefully)
// < 2048x2048 -> LOD0, >=2048x2048 -> LOD1, >= 4096x4096 -> LOD2, >= 8192x8192 -> LOD3
LandscapeProxy->StaticLightingLOD = FMath::DivideAndRoundUp( FMath::CeilLogTwo( ( XSize * YSize ) / ( 2048 * 2048 ) + 1 ), ( uint32 )2 );
// Register all the landscape components
LandscapeProxy->RegisterAllComponents();
/*ALandscape* Landscape = LandscapeProxy->GetLandscapeActor();
if (!Landscape)
return nullptr;*/
return LandscapeProxy;
}
void FHoudiniLandscapeUtils::GetHeightFieldLandscapeMaterials(
const FHoudiniGeoPartObject& Heightfield,
UMaterialInterface*& LandscapeMaterial,
UMaterialInterface*& LandscapeHoleMaterial )
{
LandscapeMaterial = nullptr;
LandscapeHoleMaterial = nullptr;
if ( !Heightfield.IsVolume() )
return;
std::string MarshallingAttributeNameMaterial = HAPI_UNREAL_ATTRIB_MATERIAL;
std::string MarshallingAttributeNameMaterialInstance = HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE;
std::string MarshallingAttributeNameMaterialHole = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE;
std::string MarshallingAttributeNameMaterialHoleInstance = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE;
// Get runtime settings.
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
if ( HoudiniRuntimeSettings )
{
if ( !HoudiniRuntimeSettings->MarshallingAttributeMaterial.IsEmpty() )
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeMaterial,
MarshallingAttributeNameMaterial);
if ( !HoudiniRuntimeSettings->MarshallingAttributeMaterialHole.IsEmpty() )
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeMaterialHole,
MarshallingAttributeNameMaterialHole );
}
TArray< FString > Materials;
HAPI_AttributeInfo AttribMaterials;
FHoudiniApi::AttributeInfo_Init(&AttribMaterials);
//FMemory::Memset< HAPI_AttributeInfo >( AttribMaterials, 0 );
// First, look for landscape material
{
FHoudiniEngineUtils::HapiGetAttributeDataAsString(
Heightfield, MarshallingAttributeNameMaterial.c_str(),
AttribMaterials, Materials );
// If the material attribute was not found, check the material instance attribute.
if ( !AttribMaterials.exists )
{
Materials.Empty();
FHoudiniEngineUtils::HapiGetAttributeDataAsString(
Heightfield, MarshallingAttributeNameMaterialInstance.c_str(),
AttribMaterials, Materials);
}
if ( AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL )
{
HOUDINI_LOG_WARNING( TEXT( "Landscape: unreal_material must be a primitive or detail attribute, ignoring attribute." ) );
AttribMaterials.exists = false;
Materials.Empty();
}
if ( AttribMaterials.exists && Materials.Num() > 0 )
{
// Load the material
LandscapeMaterial = Cast< UMaterialInterface >( StaticLoadObject(
UMaterialInterface::StaticClass(),
nullptr, *( Materials[ 0 ] ), nullptr, LOAD_NoWarn, nullptr ) );
}
}
Materials.Empty();
FHoudiniApi::AttributeInfo_Init(&AttribMaterials);
//FMemory::Memset< HAPI_AttributeInfo >( AttribMaterials, 0 );
// Then, for the hole_material
{
FHoudiniEngineUtils::HapiGetAttributeDataAsString(
Heightfield, MarshallingAttributeNameMaterialHole.c_str(),
AttribMaterials, Materials );
// If the material attribute was not found, check the material instance attribute.
if (!AttribMaterials.exists)
{
Materials.Empty();
FHoudiniEngineUtils::HapiGetAttributeDataAsString(
Heightfield, MarshallingAttributeNameMaterialHoleInstance.c_str(),
AttribMaterials, Materials);
}
if ( AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL )
{
HOUDINI_LOG_WARNING( TEXT( "Landscape: unreal_material must be a primitive or detail attribute, ignoring attribute." ) );
AttribMaterials.exists = false;
Materials.Empty();
}
if ( AttribMaterials.exists && Materials.Num() > 0 )
{
// Load the material
LandscapeHoleMaterial = Cast< UMaterialInterface >( StaticLoadObject(
UMaterialInterface::StaticClass(),
nullptr, *( Materials[ 0 ] ), nullptr, LOAD_NoWarn, nullptr ) );
}
}
}
bool FHoudiniLandscapeUtils::CreateLandscapeLayers(
FHoudiniCookParams& HoudiniCookParams,
const TArray< const FHoudiniGeoPartObject* >& FoundLayers,
const FHoudiniGeoPartObject& Heightfield,
const int32& LandscapeXSize, const int32& LandscapeYSize,
const TMap<FString, float>& GlobalMinimums,
const TMap<FString, float>& GlobalMaximums,
TArray<FLandscapeImportLayerInfo>& ImportLayerInfos )
{
// Verifying HoudiniCookParams validity
if ( !HoudiniCookParams.HoudiniAsset || !HoudiniCookParams.CookedTemporaryLandscapeLayers )
return false;
ImportLayerInfos.Empty();
// Get the names of all the non weight blended layers
TArray< FString > NonWeightBlendedLayerNames;
FHoudiniLandscapeUtils::GetNonWeightBlendedLayerNames( Heightfield, NonWeightBlendedLayerNames );
TArray<UPackage*> CreatedLandscapeLayerPackage;
// Try to create all the layers
ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive;
for ( TArray<const FHoudiniGeoPartObject *>::TConstIterator IterLayers( FoundLayers ); IterLayers; ++IterLayers )
{
const FHoudiniGeoPartObject * LayerGeoPartObject = *IterLayers;
if ( !LayerGeoPartObject )
continue;
if ( !LayerGeoPartObject->IsValid() )
continue;
if ( LayerGeoPartObject->AssetId == -1 )
continue;
TArray< float > FloatLayerData;
HAPI_VolumeInfo LayerVolumeInfo;
float LayerMin = 0;
float LayerMax = 0;
if ( !FHoudiniLandscapeUtils::GetHeightfieldData( *LayerGeoPartObject, FloatLayerData, LayerVolumeInfo, LayerMin, LayerMax ) )
continue;
// No need to create flat layers as Unreal will remove them afterwards..
if ( LayerMin == LayerMax )
continue;
// Get the layer's name
FString LayerString;
FHoudiniEngineString(LayerVolumeInfo.nameSH).ToFString(LayerString);
// Check if that landscape layer has been marked as unit (range in [0-1]
if ( IsUnitLandscapeLayer( *LayerGeoPartObject ) )
{
LayerMin = 0.0f;
LayerMax = 1.0f;
}
else
{
// We want to convert the layer using the global Min/Max
if ( GlobalMaximums.Contains( LayerString ) )
LayerMax = GlobalMaximums[ LayerString ];
if ( GlobalMinimums.Contains( LayerString ) )
LayerMin = GlobalMinimums[ LayerString ];
}
// Creating the ImportLayerInfo and LayerInfo objects
ObjectTools::SanitizeObjectName( LayerString );
FName LayerName( *LayerString );
FLandscapeImportLayerInfo currentLayerInfo( LayerName );
UPackage * Package = nullptr;
currentLayerInfo.LayerInfo = FHoudiniLandscapeUtils::CreateLandscapeLayerInfoObject( HoudiniCookParams, LayerString.GetCharArray().GetData(), Package, LayerGeoPartObject->PartId);
if ( !currentLayerInfo.LayerInfo || !Package )
continue;
// Convert the float data to uint8
// HF masks need their X/Y sizes swapped
if ( !FHoudiniLandscapeUtils::ConvertHeightfieldLayerToLandscapeLayer(
FloatLayerData, LayerVolumeInfo.yLength, LayerVolumeInfo.xLength,
LayerMin, LayerMax,
LandscapeXSize, LandscapeYSize,
currentLayerInfo.LayerData ) )
continue;
// We will store the data used to convert from Houdini values to int in the DebugColor
// This is the only way we'll be able to reconvert those values back to their houdini equivalent afterwards...
// R = Min, G = Max, B = Spacing, A = ?
currentLayerInfo.LayerInfo->LayerUsageDebugColor.R = LayerMin;
currentLayerInfo.LayerInfo->LayerUsageDebugColor.G = LayerMax;
currentLayerInfo.LayerInfo->LayerUsageDebugColor.B = ( LayerMax - LayerMin) / 255.0f;
currentLayerInfo.LayerInfo->LayerUsageDebugColor.A = PI;
HoudiniCookParams.CookedTemporaryLandscapeLayers->Add( Package, Heightfield );
// Visibility are by default non weight blended
if ( NonWeightBlendedLayerNames.Contains( LayerString )
|| LayerString.Equals(TEXT("visibility"), ESearchCase::IgnoreCase) )
currentLayerInfo.LayerInfo->bNoWeightBlend = true;
else
currentLayerInfo.LayerInfo->bNoWeightBlend = false;
// Mark the package dirty...
Package->MarkPackageDirty();
CreatedLandscapeLayerPackage.Add( Package );
ImportLayerInfos.Add( currentLayerInfo );
}
// Autosaving the layers prevents them for being deleted with the Asset
// Save the packages created for the LayerInfos
FEditorFileUtils::PromptForCheckoutAndSave( CreatedLandscapeLayerPackage, true, false );
return true;
}
ULandscapeLayerInfoObject *
FHoudiniLandscapeUtils::CreateLandscapeLayerInfoObject( FHoudiniCookParams& HoudiniCookParams, const TCHAR* LayerName, UPackage*& Package , HAPI_PartId PartId)
{
// Verifying HoudiniCookParams validity
if ( !HoudiniCookParams.HoudiniAsset || HoudiniCookParams.HoudiniAsset->IsPendingKill() )
return nullptr;
FString ComponentGUIDString = HoudiniCookParams.PackageGUID.ToString().Left( FHoudiniEngineUtils::PackageGUIDComponentNameLength );
FString LayerNameString = FString::Printf( TEXT( "%s_%d" ), LayerName, (int32)PartId );
LayerNameString = UPackageTools::SanitizePackageName( LayerNameString );
// Create the LandscapeInfoObjectName from the Asset name and the mask name
FName LayerObjectName = FName( * (HoudiniCookParams.HoudiniAsset->GetName() + ComponentGUIDString + TEXT( "_LayerInfoObject_" ) + LayerNameString ) );
// Save the package in the temp folder
FString Path = HoudiniCookParams.TempCookFolder.ToString() + TEXT( "/" );
FString PackageName = Path + LayerObjectName.ToString();
PackageName = UPackageTools::SanitizePackageName( PackageName );
// See if package exists, if it does, reuse it
bool bCreatedPackage = false;
Package = FindPackage( nullptr, *PackageName );
if ( !Package || Package->IsPendingKill() )
{
// We need to create a new package
Package = CreatePackage( nullptr, *PackageName );
bCreatedPackage = true;
}
if ( !Package || Package->IsPendingKill() )
return nullptr;
if ( !Package->IsFullyLoaded() )
Package->FullyLoad();
ULandscapeLayerInfoObject* LayerInfo = nullptr;
if ( !bCreatedPackage )
{
// See if we can load the layer info instead of creating a new one
LayerInfo = (ULandscapeLayerInfoObject*)StaticFindObjectFast(ULandscapeLayerInfoObject::StaticClass(), Package, LayerObjectName);
}
if ( !LayerInfo || LayerInfo->IsPendingKill() )
{
// Create a new LandscapeLayerInfoObject in the package
LayerInfo = NewObject<ULandscapeLayerInfoObject>(Package, LayerObjectName, RF_Public | RF_Standalone /*| RF_Transactional*/);
// Notify the asset registry
FAssetRegistryModule::AssetCreated(LayerInfo);
}
if ( LayerInfo && !LayerInfo->IsPendingKill() )
{
LayerInfo->LayerName = LayerName;
// Trigger update of the Layer Info
LayerInfo->PreEditChange(nullptr);
LayerInfo->PostEditChange();
LayerInfo->MarkPackageDirty();
// Mark the package dirty...
Package->MarkPackageDirty();
}
return LayerInfo;
}
bool FHoudiniLandscapeUtils::AddLandscapeMaterialAttributesToVolume(
const HAPI_NodeId& VolumeNodeId, const HAPI_PartId& PartId,
UMaterialInterface* LandscapeMaterial, UMaterialInterface* LandscapeHoleMaterial )
{
if ( !FHoudiniEngineUtils::IsValidNodeId(VolumeNodeId) )
return false;
// LANDSCAPE MATERIAL
if ( LandscapeMaterial && !LandscapeMaterial->IsPendingKill() )
{
// Extract the path name from the material interface
FString LandscapeMaterialString = LandscapeMaterial->GetPathName();
// Get name of attribute used for marshalling materials.
std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL;
// Marshall in material names.
HAPI_AttributeInfo AttributeInfoMaterial;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial);
//FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial);
AttributeInfoMaterial.count = 1;
AttributeInfoMaterial.tupleSize = 1;
AttributeInfoMaterial.exists = true;
AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM;
AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING;
AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID;
HAPI_Result Result = FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId,
MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial);
if ( HAPI_RESULT_SUCCESS == Result )
{
// Convert the FString to cont char *
std::string LandscapeMatCStr = TCHAR_TO_ANSI(*LandscapeMaterialString);
const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str();
TArray<const char *> LandscapeMatArr;
LandscapeMatArr.Add( LandscapeMatCStrRaw );
// Set the attribute's string data
Result = FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId,
MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial,
LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count);
}
if( Result != HAPI_RESULT_SUCCESS )
{
// Failed to create the attribute
HOUDINI_LOG_WARNING(
TEXT("Failed to upload unreal_material attribute for landscape: %s"),
*FHoudiniEngineUtils::GetErrorDescription());
}
}
// HOLE MATERIAL
if ( LandscapeHoleMaterial && !LandscapeHoleMaterial->IsPendingKill() )
{
// Extract the path name from the material interface
FString LandscapeMaterialString = LandscapeHoleMaterial->GetPathName();
// Get name of attribute used for marshalling materials.
std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE;
// Marshall in material names.
HAPI_AttributeInfo AttributeInfoMaterial;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial);
//FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial);
AttributeInfoMaterial.count = 1;
AttributeInfoMaterial.tupleSize = 1;
AttributeInfoMaterial.exists = true;
AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM;
AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING;
AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID;
HAPI_Result Result = FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId,
MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial);
if ( Result == HAPI_RESULT_SUCCESS )
{
// Convert the FString to cont char *
std::string LandscapeMatCStr = TCHAR_TO_ANSI(*LandscapeMaterialString);
const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str();
TArray<const char *> LandscapeMatArr;
LandscapeMatArr.Add(LandscapeMatCStrRaw);
// Set the attribute's string data
Result = FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId,
MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial,
LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count);
}
if ( Result != HAPI_RESULT_SUCCESS )
{
// Failed to create the attribute
HOUDINI_LOG_WARNING(
TEXT("Failed to upload unreal_material attribute for landscape: %s"),
*FHoudiniEngineUtils::GetErrorDescription());
}
}
return true;
}
bool FHoudiniLandscapeUtils::AddLandscapeGlobalMaterialAttribute( const HAPI_NodeId& NodeId, ALandscapeProxy * LandscapeProxy )
{
if ( !LandscapeProxy )
return false;
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
// Get name of attribute used for marshalling materials.
std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL;
if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeMaterial.IsEmpty() )
{
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeMaterial,
MarshallingAttributeMaterialName );
}
// If there's a global landscape material, we marshall it as detail.
UMaterialInterface * MaterialInterface = LandscapeProxy->GetLandscapeMaterial();
const char * MaterialNameStr = "";
if ( MaterialInterface )
{
FString FullMaterialName = MaterialInterface->GetPathName();
MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName);
}
HAPI_AttributeInfo AttributeInfoDetailMaterial;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterial);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterial );
AttributeInfoDetailMaterial.count = 1;
AttributeInfoDetailMaterial.tupleSize = 1;
AttributeInfoDetailMaterial.exists = true;
AttributeInfoDetailMaterial.owner = HAPI_ATTROWNER_DETAIL;
AttributeInfoDetailMaterial.storage = HAPI_STORAGETYPE_STRING;
AttributeInfoDetailMaterial.originalOwner = HAPI_ATTROWNER_INVALID;
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(), NodeId, 0,
MarshallingAttributeMaterialName.c_str(), &AttributeInfoDetailMaterial ), false);
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(), NodeId, 0,
MarshallingAttributeMaterialName.c_str(), &AttributeInfoDetailMaterial,
(const char**)&MaterialNameStr, 0, AttributeInfoDetailMaterial.count ), false );
// Get name of attribute used for marshalling hole materials.
std::string MarshallingAttributeMaterialHoleName = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE;
if ( HoudiniRuntimeSettings && !HoudiniRuntimeSettings->MarshallingAttributeMaterialHole.IsEmpty() )
{
FHoudiniEngineUtils::ConvertUnrealString(
HoudiniRuntimeSettings->MarshallingAttributeMaterialHole,
MarshallingAttributeMaterialHoleName );
}
// If there's a global landscape hole material, we marshall it as detail.
UMaterialInterface * HoleMaterialInterface = LandscapeProxy->GetLandscapeHoleMaterial();
const char * HoleMaterialNameStr = "";
if ( HoleMaterialInterface )
{
FString FullMaterialName = HoleMaterialInterface->GetPathName();
MaterialNameStr = TCHAR_TO_UTF8( *FullMaterialName );
}
HAPI_AttributeInfo AttributeInfoDetailMaterialHole;
FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterialHole);
//FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterialHole );
AttributeInfoDetailMaterialHole.count = 1;
AttributeInfoDetailMaterialHole.tupleSize = 1;
AttributeInfoDetailMaterialHole.exists = true;
AttributeInfoDetailMaterialHole.owner = HAPI_ATTROWNER_DETAIL;
AttributeInfoDetailMaterialHole.storage = HAPI_STORAGETYPE_STRING;
AttributeInfoDetailMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID;
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute(
FHoudiniEngine::Get().GetSession(),
NodeId, 0, MarshallingAttributeMaterialHoleName.c_str(),
&AttributeInfoDetailMaterialHole), false );
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData(
FHoudiniEngine::Get().GetSession(),
NodeId, 0, MarshallingAttributeMaterialHoleName.c_str(),
&AttributeInfoDetailMaterialHole, (const char **)&HoleMaterialNameStr, 0,
AttributeInfoDetailMaterialHole.count ), false );
return true;
}
bool
FHoudiniLandscapeUtils::UpdateOldLandscapeReference(ALandscapeProxy* OldLandscape, ALandscapeProxy* NewLandscape)
{
if ( !OldLandscape || !NewLandscape )
return false;
bool bReturn = false;
// Iterates through all the Houdini Assets in the scene
UWorld* editorWorld = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
if ( !editorWorld )
return false;
for ( TActorIterator<AHoudiniAssetActor> ActorItr( editorWorld ); ActorItr; ++ActorItr )
{
AHoudiniAssetActor* Actor = *ActorItr;
UHoudiniAssetComponent * HoudiniAssetComponent = Actor->GetHoudiniAssetComponent();
if ( !HoudiniAssetComponent || !HoudiniAssetComponent->IsValidLowLevel() )
{
HOUDINI_LOG_ERROR(TEXT("Failed to export a Houdini Asset in the scene!"));
continue;
}
if ( HoudiniAssetComponent->IsTemplate() )
continue;
if ( HoudiniAssetComponent->IsPendingKillOrUnreachable() )
continue;
if ( !HoudiniAssetComponent->GetOuter() )
continue;
bReturn = HoudiniAssetComponent->ReplaceLandscapeInInputs( OldLandscape, NewLandscape );
}
return bReturn;
}
bool
FHoudiniLandscapeUtils::InitDefaultHeightfieldMask(
const HAPI_VolumeInfo& HeightVolumeInfo,
const HAPI_NodeId& MaskVolumeNodeId )
{
// We need to have a mask layer as it is required for proper heightfield functionalities
// Creating an array filled with 0.0
TArray< float > MaskFloatData;
MaskFloatData.Init( 0.0f, HeightVolumeInfo.xLength * HeightVolumeInfo.yLength );
// Creating the volume infos
HAPI_VolumeInfo MaskVolumeInfo = HeightVolumeInfo;
// Set the heighfield data in Houdini
FString MaskName = TEXT("mask");
HAPI_PartId PartId = 0;
if ( !SetHeighfieldData( MaskVolumeNodeId, PartId, MaskFloatData, MaskVolumeInfo, MaskName ) )
return false;
return true;
}
bool
FHoudiniLandscapeUtils::BackupLandscapeToFile(const FString& BaseName, ALandscapeProxy* Landscape)
{
// We need to cache the input landscape to a file
if ( !Landscape )
return false;
ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo();
if (!LandscapeInfo)
return false;
// Save Height data to file
//FString HeightSave = TEXT("/Game/HoudiniEngine/Temp/HeightCache.png");
FString HeightSave = BaseName + TEXT("_height.png");
LandscapeInfo->ExportHeightmap(HeightSave);
Landscape->ReimportHeightmapFilePath = HeightSave;
// Save each layer to a file
for ( int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++ )
{
FName CurrentLayerName = LandscapeInfo->Layers[LayerIndex].GetLayerName();
//ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->GetLayerInfoByName(CurrentLayerName, Landscape);
ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj;
if ( !CurrentLayerInfo || CurrentLayerInfo->IsPendingKill() )
continue;
FString LayerSave = BaseName + CurrentLayerName.ToString() + TEXT(".png");
LandscapeInfo->ExportLayer(CurrentLayerInfo, LayerSave);
// Update the file reimport path on the input landscape for this layer
LandscapeInfo->GetLayerEditorSettings(CurrentLayerInfo).ReimportLayerFilePath = LayerSave;
}
return true;
}
bool
FHoudiniLandscapeUtils::RestoreLandscapeFromFile( ALandscapeProxy* LandscapeProxy )
{
if ( !LandscapeProxy )
return false;
ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo();
if (!LandscapeInfo)
return false;
// Restore Height data from the backup file
FString ReimportFile = LandscapeProxy->ReimportHeightmapFilePath;
if ( !ImportLandscapeData(LandscapeInfo, ReimportFile, TEXT("height") ) )
HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data."));
// Restore each layer from the backup file
TArray< ULandscapeLayerInfoObject* > SourceLayers;
for ( int LayerIndex = 0; LayerIndex < LandscapeProxy->EditorLayerSettings.Num(); LayerIndex++ )
{
ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeProxy->EditorLayerSettings[LayerIndex].LayerInfoObj;
if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill())
continue;
FString CurrentLayerName = CurrentLayerInfo->LayerName.ToString();
ReimportFile = LandscapeProxy->EditorLayerSettings[LayerIndex].ReimportLayerFilePath;
if (!ImportLandscapeData(LandscapeInfo, ReimportFile, CurrentLayerName, CurrentLayerInfo))
HOUDINI_LOG_ERROR( TEXT("Could not restore the landscape actor's source height data.") );
SourceLayers.Add( CurrentLayerInfo );
}
// Iterate on the landscape info's layer to remove any layer that could have been added by Houdini
for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++)
{
ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj;
if ( SourceLayers.Contains( CurrentLayerInfo ) )
continue;
// Delete the added layer
FName LayerName = LandscapeInfo->Layers[LayerIndex].LayerName;
LandscapeInfo->DeleteLayer(CurrentLayerInfo, LayerName);
}
return true;
}
bool
FHoudiniLandscapeUtils::ImportLandscapeData(
ULandscapeInfo* LandscapeInfo, const FString& Filename, const FString& LayerName, ULandscapeLayerInfoObject* LayerInfoObject )
{
//
// Code copied/edited from FEdModeLandscape::ImportData as we cannot access that function
//
if ( !LandscapeInfo )
return false;
bool IsHeight = LayerName.Equals(TEXT("height"), ESearchCase::IgnoreCase);
int32 MinX, MinY, MaxX, MaxY;
if (LandscapeInfo && LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY))
{
const FLandscapeFileResolution LandscapeResolution = { (uint32)(1 + MaxX - MinX), (uint32)(1 + MaxY - MinY) };
ILandscapeEditorModule& LandscapeEditorModule = FModuleManager::GetModuleChecked<ILandscapeEditorModule>("LandscapeEditor");
if ( IsHeight )
{
const ILandscapeHeightmapFileFormat* HeightmapFormat = LandscapeEditorModule.GetHeightmapFormatByExtension(*FPaths::GetExtension(Filename, true));
if (!HeightmapFormat)
{
HOUDINI_LOG_ERROR( TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName );
return false;
}
FLandscapeFileResolution ImportResolution = { 0, 0 };
const FLandscapeHeightmapInfo HeightmapInfo = HeightmapFormat->Validate(*Filename);
// display error message if there is one, and abort the import
if (HeightmapInfo.ResultCode == ELandscapeImportResult::Error)
{
HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString()) );
return false;
}
// if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape
if (HeightmapInfo.PossibleResolutions.Num() > 1)
{
if (!HeightmapInfo.PossibleResolutions.Contains(LandscapeResolution))
{
HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The heightmap file does not match the Landscape extent and its exact resolution could not be determined"));
return false;
}
else
{
ImportResolution = LandscapeResolution;
}
}
// display warning message if there is one and allow user to cancel
if (HeightmapInfo.ResultCode == ELandscapeImportResult::Warning)
HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString()));
// if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape
// unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is
if (HeightmapInfo.PossibleResolutions.Num() == 1)
{
ImportResolution = HeightmapInfo.PossibleResolutions[0];
if (ImportResolution != LandscapeResolution)
HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName);
}
FLandscapeHeightmapImportData ImportData = HeightmapFormat->Import(*Filename, ImportResolution);
if (ImportData.ResultCode == ELandscapeImportResult::Error)
{
HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString()));
return false;
}
TArray<uint16> Data;
if (ImportResolution != LandscapeResolution)
{
// Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked
// so that reimports behave the same as the initial import :)
const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2;
const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2;
Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint16));
ExpandData<uint16>(Data.GetData(), ImportData.Data.GetData(),
0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1,
-OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1);
}
else
{
Data = MoveTemp(ImportData.Data);
}
//FScopedTransaction Transaction(TEXT("Undo_ImportHeightmap", "Importing Landscape Heightmap"));
FHeightmapAccessor<false> HeightmapAccessor(LandscapeInfo);
HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData());
}
else
{
// We're importing a Landscape layer
if ( !LayerInfoObject || LayerInfoObject->IsPendingKill() )
return false;
const ILandscapeWeightmapFileFormat* WeightmapFormat = LandscapeEditorModule.GetWeightmapFormatByExtension(*FPaths::GetExtension(Filename, true));
if (!WeightmapFormat)
{
HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName);
return false;
}
FLandscapeFileResolution ImportResolution = { 0, 0 };
const FLandscapeWeightmapInfo WeightmapInfo = WeightmapFormat->Validate(*Filename, FName(*LayerName));
// display error message if there is one, and abort the import
if (WeightmapInfo.ResultCode == ELandscapeImportResult::Error)
{
HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString()));
return false;
}
// if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape
if (WeightmapInfo.PossibleResolutions.Num() > 1)
{
if (!WeightmapInfo.PossibleResolutions.Contains(LandscapeResolution))
{
HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The weightmap file does not match the Landscape extent and its exact resolution could not be determined"));
return false;
}
else
{
ImportResolution = LandscapeResolution;
}
}
// display warning message if there is one and allow user to cancel
if (WeightmapInfo.ResultCode == ELandscapeImportResult::Warning)
HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString()));
// if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape
// unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is
if (WeightmapInfo.PossibleResolutions.Num() == 1)
{
ImportResolution = WeightmapInfo.PossibleResolutions[0];
if (ImportResolution != LandscapeResolution)
HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName);
}
FLandscapeWeightmapImportData ImportData = WeightmapFormat->Import(*Filename, FName(*LayerName), ImportResolution);
if (ImportData.ResultCode == ELandscapeImportResult::Error)
{
HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString()));
return false;
}
TArray<uint8> Data;
if (ImportResolution != LandscapeResolution)
{
// Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked
// so that reimports behave the same as the initial import :)
const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2;
const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2;
Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint8));
ExpandData<uint8>(Data.GetData(), ImportData.Data.GetData(),
0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1,
-OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1);
}
else
{
Data = MoveTemp(ImportData.Data);
}
//FScopedTransaction Transaction(LOCTEXT("Undo_ImportWeightmap", "Importing Landscape Layer"));
FAlphamapAccessor<false, false> AlphamapAccessor(LandscapeInfo, LayerInfoObject);
AlphamapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData(), ELandscapeLayerPaintingRestriction::None);
}
}
return true;
}
#endif
bool
FHoudiniLandscapeUtils::IsUnitLandscapeLayer(const FHoudiniGeoPartObject& LayerGeoPartObject)
{
// Check the attribute exists on primitive or detail
HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID;
if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject, "unreal_unit_landscape_layer", HAPI_ATTROWNER_PRIM))
Owner = HAPI_ATTROWNER_PRIM;
else if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject, "unreal_unit_landscape_layer", HAPI_ATTROWNER_DETAIL))
Owner = HAPI_ATTROWNER_DETAIL;
else
return false;
// Check the value
HAPI_AttributeInfo AttribInfoUnitLayer;
FHoudiniApi::AttributeInfo_Init(&AttribInfoUnitLayer);
TArray< int32 > AttribValues;
FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(
LayerGeoPartObject, "unreal_unit_landscape_layer", AttribInfoUnitLayer, AttribValues, 1, Owner);
if (AttribValues.Num() > 0 && AttribValues[0] == 1 )
return true;
return false;
}