/* * 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 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& GlobalMinimums, TMap& 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& 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& FloatLayerData, const int32& HoudiniXSize, const int32& HoudiniYSize, const float& LayerMin, const float& LayerMax, const int32& LandscapeXSize, const int32& LandscapeYSize, TArray& LayerData, const bool& NoResize ) { // Convert the float data to uint8 LayerData.SetNumUninitialized( HoudiniXSize * HoudiniYSize ); // Calculating the factor used to convert from Houdini's ZRange to [0 255] double LayerZRange = ( LayerMax - LayerMin ); double LayerZSpacing = ( LayerZRange != 0.0 ) ? ( 255.0 / (double)( LayerZRange ) ) : 0.0; int32 nUnrealIndex = 0; for ( int32 nY = 0; nY < HoudiniYSize; nY++ ) { for ( int32 nX = 0; nX < HoudiniXSize; nX++ ) { // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y int32 nHoudini = nY + nX * HoudiniYSize; // Get the double values in [0 - ZRange] double DoubleValue = (double)FMath::Clamp(FloatLayerData[ nHoudini ], LayerMin, LayerMax) - (double)LayerMin; // Then convert it to [0 - 255] DoubleValue *= LayerZSpacing; LayerData[ nUnrealIndex++ ] = FMath::RoundToInt( DoubleValue ); } } // Finally, resize the data to fit with the new landscape size if needed if ( NoResize ) return true; return FHoudiniLandscapeUtils::ResizeLayerDataForLandscape( LayerData, HoudiniXSize, HoudiniYSize, LandscapeXSize, LandscapeYSize ); } bool FHoudiniLandscapeUtils::GetNonWeightBlendedLayerNames( const FHoudiniGeoPartObject& HeightfieldGeoPartObject, TArray& 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 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 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 void ExpandData( T* OutData, const T* InData, int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY ) { const int32 OldWidth = OldMaxX - OldMinX + 1; const int32 OldHeight = OldMaxY - OldMinY + 1; const int32 NewWidth = NewMaxX - NewMinX + 1; const int32 NewHeight = NewMaxY - NewMinY + 1; const int32 OffsetX = NewMinX - OldMinX; const int32 OffsetY = NewMinY - OldMinY; for ( int32 Y = 0; Y < NewHeight; ++Y ) { const int32 OldY = FMath::Clamp( Y + OffsetY, 0, OldHeight - 1 ); // Pad anything to the left const T PadLeft = InData[ OldY * OldWidth + 0 ]; for ( int32 X = 0; X < -OffsetX; ++X ) { OutData[ Y * NewWidth + X ] = PadLeft; } // Copy one row of the old data { const int32 X = FMath::Max( 0, -OffsetX ); const int32 OldX = FMath::Clamp( X + OffsetX, 0, OldWidth - 1 ); FMemory::Memcpy( &OutData[ Y * NewWidth + X ], &InData[ OldY * OldWidth + OldX ], FMath::Min( OldWidth, NewWidth ) * sizeof( T ) ); } const T PadRight = InData[ OldY * OldWidth + OldWidth - 1 ]; for ( int32 X = -OffsetX + OldWidth; X < NewWidth; ++X ) { OutData[ Y * NewWidth + X ] = PadRight; } } } template TArray ExpandData(const TArray& Data, int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY, int32* PadOffsetX = nullptr, int32* PadOffsetY = nullptr ) { const int32 NewWidth = NewMaxX - NewMinX + 1; const int32 NewHeight = NewMaxY - NewMinY + 1; TArray Result; Result.Empty( NewWidth * NewHeight ); Result.AddUninitialized( NewWidth * NewHeight ); ExpandData( Result.GetData(), Data.GetData(), OldMinX, OldMinY, OldMaxX, OldMaxY, NewMinX, NewMinY, NewMaxX, NewMaxY ); // Return the padding so we can offset the terrain position after if ( PadOffsetX ) *PadOffsetX = NewMinX; if ( PadOffsetY ) *PadOffsetY = NewMinY; return Result; } template TArray ResampleData( const TArray& Data, int32 OldWidth, int32 OldHeight, int32 NewWidth, int32 NewHeight ) { TArray Result; Result.Empty( NewWidth * NewHeight ); Result.AddUninitialized( NewWidth * NewHeight ); const float XScale = (float)( OldWidth - 1 ) / ( NewWidth - 1 ); const float YScale = (float)( OldHeight - 1 ) / ( NewHeight - 1 ); for ( int32 Y = 0; Y < NewHeight; ++Y ) { for ( int32 X = 0; X < NewWidth; ++X ) { const float OldY = Y * YScale; const float OldX = X * XScale; const int32 X0 = FMath::FloorToInt( OldX ); const int32 X1 = FMath::Min( FMath::FloorToInt( OldX ) + 1, OldWidth - 1 ); const int32 Y0 = FMath::FloorToInt( OldY ); const int32 Y1 = FMath::Min( FMath::FloorToInt( OldY ) + 1, OldHeight - 1 ); const T& Original00 = Data[ Y0 * OldWidth + X0 ]; const T& Original10 = Data[ Y0 * OldWidth + X1 ]; const T& Original01 = Data[ Y1 * OldWidth + X0 ]; const T& Original11 = Data[ Y1 * OldWidth + X1 ]; Result[ Y * NewWidth + X ] = FMath::BiLerp( Original00, Original10, Original01, Original11, FMath::Fractional( OldX ), FMath::Fractional( OldY ) ); } } return Result; } //------------------------------------------------------------------------------------------------------------------- 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& 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 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 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 HeightData; int32 XSize, YSize; FVector Min, Max; if ( !GetLandscapeData( LandscapeProxy, HeightData, XSize, YSize, Min, Max ) ) return false; //-------------------------------------------------------------------------------------------------- // 2. Convert the height uint16 data to float //-------------------------------------------------------------------------------------------------- TArray HeightfieldFloatValues; HAPI_VolumeInfo HeightfieldVolumeInfo; FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); FTransform LandscapeTransform = LandscapeProxy->ActorToWorld(); FVector CenterOffset = FVector::ZeroVector; if ( !ConvertLandscapeDataToHeightfieldData( HeightData, XSize, YSize, Min, Max, LandscapeTransform, HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset ) ) return false; //-------------------------------------------------------------------------------------------------- // 3. Create the Heightfield Input Node //-------------------------------------------------------------------------------------------------- HAPI_NodeId HeightFieldId = -1; HAPI_NodeId HeightId = -1; HAPI_NodeId MaskId = -1; HAPI_NodeId MergeId = -1; if ( !CreateHeightfieldInputNode( -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 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 HeightData; int32 XSize, YSize; if ( !GetLandscapeData( LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize ) ) return false; FVector Origin = LandscapeComponent->Bounds.Origin; FVector Extents = LandscapeComponent->Bounds.BoxExtent; FVector Min = Origin - Extents; FVector Max = Origin + Extents; //-------------------------------------------------------------------------------------------------- // 2. Convert the landscape's height uint16 data to float //-------------------------------------------------------------------------------------------------- TArray HeightfieldFloatValues; HAPI_VolumeInfo HeightfieldVolumeInfo; FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); FTransform LandscapeComponentTransform = LandscapeComponent->GetComponentTransform(); FVector CenterOffset = FVector::ZeroVector; if ( !ConvertLandscapeDataToHeightfieldData( HeightData, XSize, YSize, Min, Max, LandscapeComponentTransform, HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset ) ) return false; // We need to modify the Volume's position to the Component's position relative to the Landscape's position FVector RelativePosition = LandscapeComponent->GetRelativeTransform().GetLocation(); HeightfieldVolumeInfo.transform.position[1] = RelativePosition.X; HeightfieldVolumeInfo.transform.position[0] = RelativePosition.Y; HeightfieldVolumeInfo.transform.position[2] = 0.0f; //-------------------------------------------------------------------------------------------------- // 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 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& 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& 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(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(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& 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& 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& 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& IntHeightData, const int32& XSize, const int32& YSize, FVector Min, FVector Max, const FTransform& LandscapeTransform, TArray& HeightfieldFloatValues, HAPI_VolumeInfo& HeightfieldVolumeInfo, FVector& CenterOffset) { HeightfieldFloatValues.Empty(); int32 HoudiniXSize = YSize; int32 HoudiniYSize = XSize; int32 SizeInPoints = HoudiniXSize * HoudiniYSize; if ( (HoudiniXSize < 2) || (HoudiniYSize < 2) ) return false; if ( IntHeightData.Num() != SizeInPoints ) return false; // Use default unreal scaling for marshalling landscapes // A lot of precision will be lost in order to keep the same transform as the landscape input bool bUseDefaultUE4Scaling = false; const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; //-------------------------------------------------------------------------------------------------- // 1. Convert values to float //-------------------------------------------------------------------------------------------------- // Convert the min/max values from cm to meters Min /= 100.0; Max /= 100.0; // Unreal's landscape uses 16bits precision and range from -256m to 256m with the default scale of 100.0 // To convert the uint16 values to float "metric" values, offset the int by 32768 to center it, // then scale it // Spacing used to convert from uint16 to meters double ZSpacing = 512.0 / ((double)UINT16_MAX); ZSpacing *= ( (double)LandscapeTransform.GetScale3D().Z / 100.0 ); // Center value in meters (Landscape ranges from [-255:257] meters at default scale double ZCenterOffset = 32767; double ZPositionOffset = LandscapeTransform.GetLocation().Z / 100.0f; // Convert the Int data to Float HeightfieldFloatValues.SetNumUninitialized( SizeInPoints ); for ( int32 nY = 0; nY < HoudiniYSize; nY++ ) { for ( int32 nX = 0; nX < HoudiniXSize; nX++ ) { // We need to invert X/Y when reading the value from Unreal int32 nHoudini = nX + nY * HoudiniXSize; int32 nUnreal = nY + nX * XSize; // Convert the int values to meter // Unreal's digit value have a zero value of 32768 double DoubleValue = ((double)IntHeightData[nUnreal] - ZCenterOffset) * ZSpacing + ZPositionOffset; HeightfieldFloatValues[nHoudini] = (float)DoubleValue; } } //-------------------------------------------------------------------------------------------------- // 2. Convert the Unreal Transform to a HAPI_transform //-------------------------------------------------------------------------------------------------- HAPI_Transform HapiTransform; FHoudiniApi::Transform_Init(&HapiTransform); //FMemory::Memzero< HAPI_Transform >( HapiTransform ); { FQuat Rotation = LandscapeTransform.GetRotation(); if ( Rotation != FQuat::Identity ) { //Swap(ObjectRotation.Y, ObjectRotation.Z); HapiTransform.rotationQuaternion[0] = Rotation.X; HapiTransform.rotationQuaternion[1] = Rotation.Z; HapiTransform.rotationQuaternion[2] = Rotation.Y; HapiTransform.rotationQuaternion[3] = -Rotation.W; } else { HapiTransform.rotationQuaternion[0] = 0; HapiTransform.rotationQuaternion[1] = 0; HapiTransform.rotationQuaternion[2] = 0; HapiTransform.rotationQuaternion[3] = 1; } // Heightfield are centered, landscapes are not CenterOffset = (Max - Min) * 0.5f; // Unreal XYZ becomes Houdini YXZ (since heightfields are also rotated due the ZX transform) //FVector Position = LandscapeTransform.GetLocation() / 100.0f; HapiTransform.position[ 1 ] = 0.0f;//Position.X + CenterOffset.X; HapiTransform.position[ 0 ] = 0.0f;//Position.Y + CenterOffset.Y; HapiTransform.position[ 2 ] = 0.0f; FVector Scale = LandscapeTransform.GetScale3D() / 100.0f; HapiTransform.scale[ 0 ] = Scale.X * 0.5f * HoudiniXSize; HapiTransform.scale[ 1 ] = Scale.Y * 0.5f * HoudiniYSize; HapiTransform.scale[ 2 ] = 0.5f; if ( bUseDefaultUE4Scaling ) HapiTransform.scale[2] *= Scale.Z; HapiTransform.shear[ 0 ] = 0.0f; HapiTransform.shear[ 1 ] = 0.0f; HapiTransform.shear[ 2 ] = 0.0f; } //-------------------------------------------------------------------------------------------------- // 3. Fill the volume info //-------------------------------------------------------------------------------------------------- HeightfieldVolumeInfo.xLength = HoudiniXSize; HeightfieldVolumeInfo.yLength = HoudiniYSize; HeightfieldVolumeInfo.zLength = 1; HeightfieldVolumeInfo.minX = 0; HeightfieldVolumeInfo.minY = 0; HeightfieldVolumeInfo.minZ = 0; HeightfieldVolumeInfo.transform = HapiTransform; HeightfieldVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; HeightfieldVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; HeightfieldVolumeInfo.tupleSize = 1; HeightfieldVolumeInfo.tileSize = 1; HeightfieldVolumeInfo.hasTaper = false; HeightfieldVolumeInfo.xTaper = 0.0; HeightfieldVolumeInfo.yTaper = 0.0; return true; } // Converts Unreal uint16 values to Houdini Float bool FHoudiniLandscapeUtils::ConvertLandscapeLayerDataToHeightfieldData( const TArray& IntHeightData, const int32& XSize, const int32& YSize, const FLinearColor& LayerUsageDebugColor, TArray& LayerFloatValues, HAPI_VolumeInfo& LayerVolumeInfo) { LayerFloatValues.Empty(); int32 HoudiniXSize = YSize; int32 HoudiniYSize = XSize; int32 SizeInPoints = HoudiniXSize * HoudiniYSize; if ( ( HoudiniXSize < 2 ) || ( HoudiniYSize < 2 ) ) return false; if ( IntHeightData.Num() != SizeInPoints ) return false; //-------------------------------------------------------------------------------------------------- // 1. Convert values to float //-------------------------------------------------------------------------------------------------- // 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& 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& 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& SelectedComponents, const bool& bExportLighting, const bool& bExportTileUVs, const bool& bExportNormalizedUVs, TArray& LandscapePositionArray, TArray& LandscapeNormalArray, TArray& LandscapeUVArray, TArray& LandscapeComponentVertexIndicesArray, TArray& LandscapeComponentNameArray, TArray& LandscapeLightmapValues ) { if ( !LandscapeProxy ) return false; if ( SelectedComponents.Num() < 1 ) return false; // Get runtime settings. const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); 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& 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& 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& 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& 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& 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 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 >& Landscapes, TMap< FHoudiniGeoPartObject, TWeakObjectPtr >& NewLandscapes, TArray& 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 GlobalMinimums; TMap 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 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* 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 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 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(); else LandscapeProxy = MyWorld->SpawnActor(); 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> HeightmapDataPerLayers; TMap> 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& GlobalMinimums, const TMap& GlobalMaximums, TArray& 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 CreatedLandscapeLayerPackage; // Try to create all the layers ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; for ( TArray::TConstIterator IterLayers( FoundLayers ); IterLayers; ++IterLayers ) { const FHoudiniGeoPartObject * LayerGeoPartObject = *IterLayers; if ( !LayerGeoPartObject ) continue; if ( !LayerGeoPartObject->IsValid() ) continue; if ( 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(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 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 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 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("LandscapeEditor"); if ( IsHeight ) { const ILandscapeHeightmapFileFormat* HeightmapFormat = LandscapeEditorModule.GetHeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); if (!HeightmapFormat) { HOUDINI_LOG_ERROR( TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName ); return false; } FLandscapeFileResolution ImportResolution = { 0, 0 }; const FLandscapeHeightmapInfo HeightmapInfo = HeightmapFormat->Validate(*Filename); // display error message if there is one, and abort the import if (HeightmapInfo.ResultCode == ELandscapeImportResult::Error) { HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString()) ); return false; } // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape if (HeightmapInfo.PossibleResolutions.Num() > 1) { if (!HeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) { HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The heightmap file does not match the Landscape extent and its exact resolution could not be determined")); return false; } else { ImportResolution = LandscapeResolution; } } // display warning message if there is one and allow user to cancel if (HeightmapInfo.ResultCode == ELandscapeImportResult::Warning) HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is if (HeightmapInfo.PossibleResolutions.Num() == 1) { ImportResolution = HeightmapInfo.PossibleResolutions[0]; if (ImportResolution != LandscapeResolution) HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); } FLandscapeHeightmapImportData ImportData = HeightmapFormat->Import(*Filename, ImportResolution); if (ImportData.ResultCode == ELandscapeImportResult::Error) { HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); return false; } TArray Data; if (ImportResolution != LandscapeResolution) { // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked // so that reimports behave the same as the initial import :) const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint16)); ExpandData(Data.GetData(), ImportData.Data.GetData(), 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); } else { Data = MoveTemp(ImportData.Data); } //FScopedTransaction Transaction(TEXT("Undo_ImportHeightmap", "Importing Landscape Heightmap")); FHeightmapAccessor HeightmapAccessor(LandscapeInfo); HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData()); } else { // We're importing a Landscape layer if ( !LayerInfoObject || LayerInfoObject->IsPendingKill() ) return false; const ILandscapeWeightmapFileFormat* WeightmapFormat = LandscapeEditorModule.GetWeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); if (!WeightmapFormat) { HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); return false; } FLandscapeFileResolution ImportResolution = { 0, 0 }; const FLandscapeWeightmapInfo WeightmapInfo = WeightmapFormat->Validate(*Filename, FName(*LayerName)); // display error message if there is one, and abort the import if (WeightmapInfo.ResultCode == ELandscapeImportResult::Error) { HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); return false; } // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape if (WeightmapInfo.PossibleResolutions.Num() > 1) { if (!WeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) { HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The weightmap file does not match the Landscape extent and its exact resolution could not be determined")); return false; } else { ImportResolution = LandscapeResolution; } } // display warning message if there is one and allow user to cancel if (WeightmapInfo.ResultCode == ELandscapeImportResult::Warning) HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is if (WeightmapInfo.PossibleResolutions.Num() == 1) { ImportResolution = WeightmapInfo.PossibleResolutions[0]; if (ImportResolution != LandscapeResolution) HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); } FLandscapeWeightmapImportData ImportData = WeightmapFormat->Import(*Filename, FName(*LayerName), ImportResolution); if (ImportData.ResultCode == ELandscapeImportResult::Error) { HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); return false; } TArray Data; if (ImportResolution != LandscapeResolution) { // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked // so that reimports behave the same as the initial import :) const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint8)); ExpandData(Data.GetData(), ImportData.Data.GetData(), 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); } else { Data = MoveTemp(ImportData.Data); } //FScopedTransaction Transaction(LOCTEXT("Undo_ImportWeightmap", "Importing Landscape Layer")); FAlphamapAccessor AlphamapAccessor(LandscapeInfo, LayerInfoObject); AlphamapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData(), ELandscapeLayerPaintingRestriction::None); } } return true; } #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; }