2022-10-16 02:28:34 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "SparseVolumeTextureFactory.h"
# include "SparseVolumeTexture/SparseVolumeTexture.h"
# if WITH_EDITOR
# include "SparseVolumeTextureOpenVDB.h"
2022-11-25 09:18:31 -05:00
# include "SparseVolumeTextureOpenVDBUtility.h"
2023-01-31 01:11:48 -05:00
# include "OpenVDBImportOptions.h"
2022-10-16 02:28:34 -04:00
# include "Serialization/EditorBulkDataWriter.h"
# include "Misc/Paths.h"
# include "Misc/ScopedSlowTask.h"
2022-11-25 09:18:31 -05:00
# include "Misc/FileHelper.h"
2023-02-16 10:42:36 -05:00
# include "Async/Async.h"
2023-02-22 09:39:24 -05:00
# include "Async/ParallelFor.h"
2022-10-16 02:28:34 -04:00
# include "Editor.h"
2022-11-30 15:06:09 -05:00
# include "OpenVDBImportWindow.h"
# include "HAL/PlatformApplicationMisc.h"
2023-02-16 10:42:36 -05:00
# include "HAL/Event.h"
# include "HAL/PlatformProcess.h"
2022-11-30 15:06:09 -05:00
# include "Interfaces/IMainFrameModule.h"
2023-02-16 10:42:36 -05:00
# include <atomic>
# include <mutex>
2022-10-16 02:28:34 -04:00
# define LOCTEXT_NAMESPACE "USparseVolumeTextureFactory"
DEFINE_LOG_CATEGORY_STATIC ( LogSparseVolumeTextureFactory , Log , All ) ;
2023-01-31 01:11:48 -05:00
static void ComputeDefaultOpenVDBGridAssignment ( const TArray < TSharedPtr < FOpenVDBGridComponentInfo > > & GridComponentInfo , int32 NumFiles , FOpenVDBImportOptions * ImportOptions )
2023-01-30 11:48:00 -05:00
{
2023-01-31 01:11:48 -05:00
for ( FOpenVDBSparseVolumeAttributesDesc & AttributesDesc : ImportOptions - > Attributes )
2023-01-30 11:48:00 -05:00
{
2023-01-31 01:11:48 -05:00
for ( FOpenVDBSparseVolumeComponentMapping & Mapping : AttributesDesc . Mappings )
{
Mapping . SourceGridIndex = INDEX_NONE ;
Mapping . SourceComponentIndex = INDEX_NONE ;
}
AttributesDesc . Format = ESparseVolumeAttributesFormat : : Float32 ;
AttributesDesc . bRemapInputForUnorm = false ;
2023-01-30 11:48:00 -05:00
}
// Assign the components of the input grids to the components of the output SVT.
2023-01-31 01:11:48 -05:00
uint32 DstAttributesIdx = 0 ;
uint32 DstComponentIdx = 0 ;
2023-01-30 11:48:00 -05:00
for ( const TSharedPtr < FOpenVDBGridComponentInfo > & GridComponent : GridComponentInfo )
{
if ( GridComponent - > Index = = INDEX_NONE )
{
continue ;
}
2023-01-31 01:11:48 -05:00
ImportOptions - > Attributes [ DstAttributesIdx ] . Mappings [ DstComponentIdx ] . SourceGridIndex = GridComponent - > Index ;
ImportOptions - > Attributes [ DstAttributesIdx ] . Mappings [ DstComponentIdx ] . SourceComponentIndex = GridComponent - > ComponentIndex ;
+ + DstComponentIdx ;
if ( DstComponentIdx = = 4 )
2023-01-30 11:48:00 -05:00
{
2023-01-31 01:11:48 -05:00
DstComponentIdx = 0 ;
+ + DstAttributesIdx ;
if ( DstAttributesIdx = = 2 )
2023-01-30 11:48:00 -05:00
{
break ;
}
}
}
2023-01-31 01:11:48 -05:00
ImportOptions - > bIsSequence = NumFiles > 1 ;
2023-01-30 11:48:00 -05:00
}
static TArray < FString > FindOpenVDBSequenceFileNames ( const FString & Filename )
{
TArray < FString > SequenceFilenames ;
// The file is potentially a sequence if the character before the `.vdb` is a number.
const bool bIsFilePotentiallyPartOfASequence = FChar : : IsDigit ( Filename [ Filename . Len ( ) - 5 ] ) ;
if ( ! bIsFilePotentiallyPartOfASequence )
{
SequenceFilenames . Add ( Filename ) ;
}
else
{
const FString Path = FPaths : : GetPath ( Filename ) ;
const FString CleanFilename = FPaths : : GetCleanFilename ( Filename ) ;
FString CleanFilenameWithoutSuffix ;
{
const FString CleanFilenameWithoutExt = CleanFilename . LeftChop ( 4 ) ;
const int32 LastNonDigitIndex = CleanFilenameWithoutExt . FindLastCharByPredicate ( [ ] ( TCHAR Letter ) { return ! FChar : : IsDigit ( Letter ) ; } ) + 1 ;
const int32 DigitCount = CleanFilenameWithoutExt . Len ( ) - LastNonDigitIndex ;
CleanFilenameWithoutSuffix = CleanFilenameWithoutExt . LeftChop ( CleanFilenameWithoutExt . Len ( ) - LastNonDigitIndex ) ;
}
// Find all files potentially part of the sequence
TArray < FString > PotentialSequenceFilenames ;
IFileManager : : Get ( ) . FindFiles ( PotentialSequenceFilenames , * Path , TEXT ( " *.vdb " ) ) ;
PotentialSequenceFilenames = PotentialSequenceFilenames . FilterByPredicate ( [ CleanFilenameWithoutSuffix ] ( const FString & Str ) { return Str . StartsWith ( CleanFilenameWithoutSuffix ) ; } ) ;
auto GetFilenameNumberSuffix = [ ] ( const FString & Filename ) - > int32
{
const FString FilenameWithoutExt = Filename . LeftChop ( 4 ) ;
const int32 LastNonDigitIndex = FilenameWithoutExt . FindLastCharByPredicate ( [ ] ( TCHAR Letter ) { return ! FChar : : IsDigit ( Letter ) ; } ) + 1 ;
const FString NumberSuffixStr = FilenameWithoutExt . RightChop ( LastNonDigitIndex ) ;
int32 Number = INDEX_NONE ;
if ( NumberSuffixStr . IsNumeric ( ) )
{
TTypeFromString < int32 > : : FromString ( Number , * NumberSuffixStr ) ;
}
return Number ;
} ;
// Find range of number suffixes
int32 LowestIndex = INT32_MAX ;
int32 HighestIndex = INT32_MIN ;
for ( FString & ItemFilename : PotentialSequenceFilenames )
{
const int32 Index = GetFilenameNumberSuffix ( ItemFilename ) ;
if ( Index = = INDEX_NONE )
{
ItemFilename . Empty ( ) ;
continue ;
}
LowestIndex = FMath : : Min ( LowestIndex , Index ) ;
HighestIndex = FMath : : Max ( HighestIndex , Index ) ;
}
check ( HighestIndex > = LowestIndex ) ;
// Sort the filenames into the result array
SequenceFilenames . SetNum ( HighestIndex - LowestIndex + 1 ) ;
for ( const FString & ItemFilename : PotentialSequenceFilenames )
{
const int32 Index = ItemFilename . IsEmpty ( ) ? INDEX_NONE : GetFilenameNumberSuffix ( ItemFilename ) ;
if ( Index = = INDEX_NONE )
{
continue ;
}
SequenceFilenames [ Index - LowestIndex ] = Path / ItemFilename ;
}
// Chop off any items after finding the first gap
for ( int32 i = 0 ; i < SequenceFilenames . Num ( ) ; + + i )
{
if ( SequenceFilenames [ i ] . IsEmpty ( ) )
{
SequenceFilenames . SetNum ( i ) ;
break ;
}
}
}
check ( ! SequenceFilenames . IsEmpty ( ) ) ;
return SequenceFilenames ;
}
2023-01-24 21:44:51 -05:00
struct FOpenVDBPreviewData
{
2023-02-23 14:53:04 -05:00
TArray64 < uint8 > LoadedFile ;
2023-01-24 21:44:51 -05:00
TArray < FOpenVDBGridInfo > GridInfo ;
2023-01-30 11:48:00 -05:00
TArray < TSharedPtr < FOpenVDBGridInfo > > GridInfoPtrs ;
2023-01-24 21:44:51 -05:00
TArray < TSharedPtr < FOpenVDBGridComponentInfo > > GridComponentInfoPtrs ;
2023-01-30 11:48:00 -05:00
TArray < FString > SequenceFilenames ;
2023-01-31 01:11:48 -05:00
FOpenVDBImportOptions DefaultImportOptions ;
2023-01-24 21:44:51 -05:00
} ;
static bool LoadOpenVDBPreviewData ( const FString & Filename , FOpenVDBPreviewData * OutPreviewData )
{
FOpenVDBPreviewData & Result = * OutPreviewData ;
2023-01-30 11:48:00 -05:00
check ( Result . LoadedFile . IsEmpty ( ) ) ;
check ( Result . GridInfo . IsEmpty ( ) ) ;
check ( Result . GridInfoPtrs . IsEmpty ( ) ) ;
check ( Result . GridComponentInfoPtrs . IsEmpty ( ) ) ;
check ( Result . SequenceFilenames . IsEmpty ( ) ) ;
2023-01-24 21:44:51 -05:00
if ( ! FFileHelper : : LoadFileToArray ( Result . LoadedFile , * Filename ) )
{
UE_LOG ( LogSparseVolumeTextureFactory , Error , TEXT ( " OpenVDB file could not be loaded: %s " ) , * Filename ) ;
return false ;
}
if ( ! GetOpenVDBGridInfo ( Result . LoadedFile , true /*bCreateStrings*/ , & Result . GridInfo ) )
{
UE_LOG ( LogSparseVolumeTextureFactory , Error , TEXT ( " Failed to read OpenVDB file: %s " ) , * Filename ) ;
return false ;
}
if ( Result . GridInfo . IsEmpty ( ) )
{
UE_LOG ( LogSparseVolumeTextureFactory , Error , TEXT ( " OpenVDB file contains no grids: %s " ) , * Filename ) ;
return false ;
}
// We need a <None> option to leave channels empty
FOpenVDBGridComponentInfo NoneGridComponentInfo ;
NoneGridComponentInfo . Index = INDEX_NONE ;
NoneGridComponentInfo . ComponentIndex = INDEX_NONE ;
NoneGridComponentInfo . Name = TEXT ( " <None> " ) ;
NoneGridComponentInfo . DisplayString = TEXT ( " <None> " ) ;
Result . GridComponentInfoPtrs . Add ( MakeShared < FOpenVDBGridComponentInfo > ( NoneGridComponentInfo ) ) ;
// Create individual entries for each component of all valid source grids.
// This is an array of TSharedPtr because SComboBox requires its input to be wrapped in TSharedPtr.
bool bFoundSupportedGridType = false ;
for ( const FOpenVDBGridInfo & Grid : Result . GridInfo )
{
2023-01-30 11:48:00 -05:00
// Append all grids, even if we don't actually support them
Result . GridInfoPtrs . Add ( MakeShared < FOpenVDBGridInfo > ( Grid ) ) ;
2023-01-24 21:44:51 -05:00
if ( Grid . Type = = EOpenVDBGridType : : Unknown | | ! IsOpenVDBGridValid ( Grid , Filename ) )
{
continue ;
}
bFoundSupportedGridType = true ;
// Create one entry per component
for ( uint32 ComponentIdx = 0 ; ComponentIdx < Grid . NumComponents ; + + ComponentIdx )
{
FOpenVDBGridComponentInfo ComponentInfo ;
ComponentInfo . Index = Grid . Index ;
ComponentInfo . ComponentIndex = ComponentIdx ;
ComponentInfo . Name = Grid . Name ;
const TCHAR * ComponentNames [ ] = { TEXT ( " .X " ) , TEXT ( " .Y " ) , TEXT ( " .Z " ) , TEXT ( " .W " ) } ;
FStringFormatOrderedArguments FormatArgs ;
FormatArgs . Add ( ComponentInfo . Index ) ;
FormatArgs . Add ( ComponentInfo . Name ) ;
FormatArgs . Add ( Grid . NumComponents = = 1 ? TEXT ( " " ) : ComponentNames [ ComponentIdx ] ) ;
ComponentInfo . DisplayString = FString : : Format ( TEXT ( " {0}. {1}{2} " ) , FormatArgs ) ;
Result . GridComponentInfoPtrs . Add ( MakeShared < FOpenVDBGridComponentInfo > ( MoveTemp ( ComponentInfo ) ) ) ;
}
}
if ( ! bFoundSupportedGridType )
{
UE_LOG ( LogSparseVolumeTextureFactory , Error , TEXT ( " OpenVDB file contains no grids of supported type: %s " ) , * Filename ) ;
return false ;
}
2023-01-30 11:48:00 -05:00
Result . SequenceFilenames = FindOpenVDBSequenceFileNames ( Filename ) ;
2023-01-31 01:11:48 -05:00
ComputeDefaultOpenVDBGridAssignment ( Result . GridComponentInfoPtrs , Result . SequenceFilenames . Num ( ) , & Result . DefaultImportOptions ) ;
2023-01-30 11:48:00 -05:00
2023-01-24 21:44:51 -05:00
return true ;
}
2023-01-30 11:48:00 -05:00
static bool ShowOpenVDBImportWindow ( const FString & Filename , const FOpenVDBPreviewData & PreviewData , FOpenVDBImportOptions * OutImportOptions )
2023-01-24 21:44:51 -05:00
{
TSharedPtr < SWindow > ParentWindow ;
if ( FModuleManager : : Get ( ) . IsModuleLoaded ( " MainFrame " ) )
{
IMainFrameModule & MainFrame = FModuleManager : : LoadModuleChecked < IMainFrameModule > ( " MainFrame " ) ;
ParentWindow = MainFrame . GetParentWindow ( ) ;
}
// Compute centered window position based on max window size, which include when all categories are expanded
const float ImportWindowWidth = 450.0f ;
const float ImportWindowHeight = 750.0f ;
FVector2D ImportWindowSize = FVector2D ( ImportWindowWidth , ImportWindowHeight ) ; // Max window size it can get based on current slate
FSlateRect WorkAreaRect = FSlateApplicationBase : : Get ( ) . GetPreferredWorkArea ( ) ;
FVector2D DisplayTopLeft ( WorkAreaRect . Left , WorkAreaRect . Top ) ;
FVector2D DisplaySize ( WorkAreaRect . Right - WorkAreaRect . Left , WorkAreaRect . Bottom - WorkAreaRect . Top ) ;
float ScaleFactor = FPlatformApplicationMisc : : GetDPIScaleFactorAtPoint ( DisplayTopLeft . X , DisplayTopLeft . Y ) ;
ImportWindowSize * = ScaleFactor ;
FVector2D WindowPosition = ( DisplayTopLeft + ( DisplaySize - ImportWindowSize ) / 2.0f ) / ScaleFactor ;
TSharedRef < SWindow > Window = SNew ( SWindow )
. Title ( NSLOCTEXT ( " UnrealEd " , " OpenVDBImportOptionsTitle " , " OpenVDB Import Options " ) )
. SizingRule ( ESizingRule : : Autosized )
. AutoCenter ( EAutoCenter : : None )
. ClientSize ( ImportWindowSize )
. ScreenPosition ( WindowPosition ) ;
2023-01-31 01:11:48 -05:00
TArray < TSharedPtr < ESparseVolumeAttributesFormat > > SupportedFormats =
2023-01-24 21:44:51 -05:00
{
2023-01-31 01:11:48 -05:00
MakeShared < ESparseVolumeAttributesFormat > ( ESparseVolumeAttributesFormat : : Float32 ) ,
MakeShared < ESparseVolumeAttributesFormat > ( ESparseVolumeAttributesFormat : : Float16 ) ,
MakeShared < ESparseVolumeAttributesFormat > ( ESparseVolumeAttributesFormat : : Unorm8 )
2023-01-24 21:44:51 -05:00
} ;
TSharedPtr < SOpenVDBImportWindow > OpenVDBOptionWindow ;
Window - > SetContent
(
SAssignNew ( OpenVDBOptionWindow , SOpenVDBImportWindow )
2023-01-31 01:11:48 -05:00
. ImportOptions ( OutImportOptions )
. DefaultImportOptions ( & PreviewData . DefaultImportOptions )
2023-01-30 11:48:00 -05:00
. NumFoundFiles ( PreviewData . SequenceFilenames . Num ( ) )
. OpenVDBGridInfo ( & PreviewData . GridInfoPtrs )
. OpenVDBGridComponentInfo ( & PreviewData . GridComponentInfoPtrs )
2023-01-24 21:44:51 -05:00
. OpenVDBSupportedTargetFormats ( & SupportedFormats )
. WidgetWindow ( Window )
. FullPath ( FText : : FromString ( Filename ) )
. MaxWindowHeight ( ImportWindowHeight )
. MaxWindowWidth ( ImportWindowWidth )
) ;
FSlateApplication : : Get ( ) . AddModalWindow ( Window , ParentWindow , false ) ;
2023-01-30 11:48:00 -05:00
OutImportOptions - > bIsSequence = OpenVDBOptionWindow - > ShouldImportAsSequence ( ) ;
2023-01-24 21:44:51 -05:00
2023-01-30 11:48:00 -05:00
return OpenVDBOptionWindow - > ShouldImport ( ) ;
}
static bool ValidateImportOptions ( const FOpenVDBImportOptions & ImportOptions , const TArray < FOpenVDBGridInfo > & GridInfo )
{
2023-01-31 01:11:48 -05:00
const int32 NumGrids = GridInfo . Num ( ) ;
for ( const FOpenVDBSparseVolumeAttributesDesc & AttributesDesc : ImportOptions . Attributes )
2023-01-30 11:48:00 -05:00
{
2023-01-31 01:11:48 -05:00
for ( const FOpenVDBSparseVolumeComponentMapping & Mapping : AttributesDesc . Mappings )
2023-01-30 11:48:00 -05:00
{
2023-01-31 01:11:48 -05:00
const int32 SourceGridIndex = Mapping . SourceGridIndex ;
const int32 SourceComponentIndex = Mapping . SourceComponentIndex ;
if ( Mapping . SourceGridIndex ! = INDEX_NONE )
2023-01-30 11:48:00 -05:00
{
if ( SourceGridIndex > = NumGrids )
{
return false ; // Invalid grid index
}
2023-01-31 01:11:48 -05:00
if ( SourceComponentIndex = = INDEX_NONE | | SourceComponentIndex > = ( int32 ) GridInfo [ SourceGridIndex ] . NumComponents )
2023-01-30 11:48:00 -05:00
{
return false ; // Invalid component index
}
}
}
}
return true ;
2023-01-24 21:44:51 -05:00
}
2022-10-16 02:28:34 -04:00
USparseVolumeTextureFactory : : USparseVolumeTextureFactory ( const FObjectInitializer & ObjectInitializer )
: Super ( ObjectInitializer )
{
bCreateNew = true ;
bEditAfterNew = true ;
bEditorImport = true ;
SupportedClass = USparseVolumeTexture : : StaticClass ( ) ;
Formats . Add ( TEXT ( " vdb;OpenVDB Format " ) ) ;
}
FText USparseVolumeTextureFactory : : GetDisplayName ( ) const
{
return LOCTEXT ( " SparseVolumeTextureFactoryDescription " , " Sparse Volume Texture " ) ;
}
bool USparseVolumeTextureFactory : : ConfigureProperties ( )
{
return true ;
}
bool USparseVolumeTextureFactory : : ShouldShowInNewMenu ( ) const
{
return false ;
}
///////////////////////////////////////////////////////////////////////////////
// Create asset
bool USparseVolumeTextureFactory : : CanCreateNew ( ) const
{
return false ; // To be able to import files and call
}
UObject * USparseVolumeTextureFactory : : FactoryCreateNew ( UClass * InClass , UObject * InParent , FName InName , EObjectFlags Flags , UObject * Context , FFeedbackContext * Warn )
{
USparseVolumeTexture * Object = NewObject < USparseVolumeTexture > ( InParent , InClass , InName , Flags ) ;
// SVT_TODO initialize similarly to UTexture2DFactoryNew
return Object ;
}
///////////////////////////////////////////////////////////////////////////////
// Import asset
bool USparseVolumeTextureFactory : : DoesSupportClass ( UClass * Class )
{
return Class = = USparseVolumeTexture : : StaticClass ( ) ;
}
UClass * USparseVolumeTextureFactory : : ResolveSupportedClass ( )
{
return USparseVolumeTexture : : StaticClass ( ) ;
}
bool USparseVolumeTextureFactory : : FactoryCanImport ( const FString & Filename )
{
const FString Extension = FPaths : : GetExtension ( Filename ) ;
if ( Extension = = TEXT ( " vdb " ) )
{
return true ;
}
return false ;
}
void USparseVolumeTextureFactory : : CleanUp ( )
{
Super : : CleanUp ( ) ;
}
UObject * USparseVolumeTextureFactory : : FactoryCreateFile ( UClass * InClass , UObject * InParent , FName InName , EObjectFlags Flags , const FString & Filename ,
const TCHAR * Parms , FFeedbackContext * Warn , bool & bOutOperationCanceled )
{
# if OPENVDB_AVAILABLE
GEditor - > GetEditorSubsystem < UImportSubsystem > ( ) - > BroadcastAssetPreImport ( this , InClass , InParent , InName , Parms ) ;
TArray < UObject * > ResultAssets ;
2023-01-30 11:48:00 -05:00
bOutOperationCanceled = false ;
const bool bIsUnattended = ( IsAutomatedImport ( )
| | FApp : : IsUnattended ( )
| | IsRunningCommandlet ( )
| | GIsRunningUnattendedScript ) ;
2023-01-24 21:44:51 -05:00
// Load file and get info about each contained grid
FOpenVDBPreviewData PreviewData ;
if ( ! LoadOpenVDBPreviewData ( Filename , & PreviewData ) )
2022-10-16 02:28:34 -04:00
{
2023-01-24 21:44:51 -05:00
return nullptr ;
}
2023-01-31 01:11:48 -05:00
FOpenVDBImportOptions ImportOptions = PreviewData . DefaultImportOptions ;
2023-01-30 11:48:00 -05:00
if ( ! bIsUnattended )
2023-01-24 21:44:51 -05:00
{
2023-01-30 11:48:00 -05:00
// Show dialog for import options
if ( ! ShowOpenVDBImportWindow ( Filename , PreviewData , & ImportOptions ) )
{
bOutOperationCanceled = true ;
return nullptr ;
}
}
if ( ! ValidateImportOptions ( ImportOptions , PreviewData . GridInfo ) )
{
UE_LOG ( LogSparseVolumeTextureFactory , Error , TEXT ( " Import options are invalid! This is likely due to invalid/out-of-bounds grid or component indices. " ) ) ;
2023-01-24 21:44:51 -05:00
return nullptr ;
}
// Utility function for computing the bounding box encompassing the bounds of all frames in the SVT.
2023-02-22 09:39:24 -05:00
auto ExpandVolumeBounds = [ ] ( const FOpenVDBImportOptions & ImportOptions , const TArray < FOpenVDBGridInfo > & GridInfoArray , FIntVector3 & VolumeBoundsMin , FIntVector3 & VolumeBoundsMax )
2023-01-24 21:44:51 -05:00
{
2023-01-31 01:11:48 -05:00
for ( const FOpenVDBSparseVolumeAttributesDesc & Attributes : ImportOptions . Attributes )
2023-01-24 21:44:51 -05:00
{
2023-01-31 01:11:48 -05:00
for ( const FOpenVDBSparseVolumeComponentMapping & Mapping : Attributes . Mappings )
2023-01-24 21:44:51 -05:00
{
2023-01-31 01:11:48 -05:00
if ( Mapping . SourceGridIndex ! = INDEX_NONE )
2023-01-24 21:44:51 -05:00
{
2023-01-31 01:11:48 -05:00
const FOpenVDBGridInfo & GridInfo = GridInfoArray [ Mapping . SourceGridIndex ] ;
2023-02-22 09:39:24 -05:00
VolumeBoundsMin . X = FMath : : Min ( VolumeBoundsMin . X , GridInfo . VolumeActiveAABBMin . X ) ;
VolumeBoundsMin . Y = FMath : : Min ( VolumeBoundsMin . Y , GridInfo . VolumeActiveAABBMin . Y ) ;
VolumeBoundsMin . Z = FMath : : Min ( VolumeBoundsMin . Z , GridInfo . VolumeActiveAABBMin . Z ) ;
2023-01-24 21:44:51 -05:00
2023-02-22 09:39:24 -05:00
VolumeBoundsMax . X = FMath : : Max ( VolumeBoundsMax . X , GridInfo . VolumeActiveAABBMax . X ) ;
VolumeBoundsMax . Y = FMath : : Max ( VolumeBoundsMax . Y , GridInfo . VolumeActiveAABBMax . Y ) ;
VolumeBoundsMax . Z = FMath : : Max ( VolumeBoundsMax . Z , GridInfo . VolumeActiveAABBMax . Z ) ;
2023-01-24 21:44:51 -05:00
}
}
}
2022-10-16 02:28:34 -04:00
} ;
2023-02-22 09:39:24 -05:00
auto ComputeNumMipLevels = [ ] ( const FIntVector3 & VolumeBoundsMin , const FIntVector3 & VolumeBoundsMax )
{
int32 Levels = 1 ;
FIntVector3 Resolution = VolumeBoundsMax - VolumeBoundsMin ;
while ( Resolution . X > 1 | | Resolution . Y > 1 | | Resolution . Z > 1 )
{
Resolution / = 2 ;
+ + Levels ;
}
return Levels ;
} ;
FIntVector3 VolumeBoundsMin = FIntVector3 ( INT32_MAX , INT32_MAX , INT32_MAX ) ;
FIntVector3 VolumeBoundsMax = FIntVector3 ( INT32_MIN , INT32_MIN , INT32_MIN ) ;
2023-01-24 21:44:51 -05:00
// Import as either single static SVT or a sequence of frames, making up an animated SVT
if ( ! ImportOptions . bIsSequence )
2022-10-16 02:28:34 -04:00
{
// Import as a static sparse volume texture asset.
FScopedSlowTask ImportTask ( 1.0f , LOCTEXT ( " ImportingVDBStatic " , " Importing static OpenVDB " ) ) ;
ImportTask . MakeDialog ( true ) ;
2023-01-24 21:44:51 -05:00
FName NewName ( InName . ToString ( ) + TEXT ( " VDB " ) ) ;
UStaticSparseVolumeTexture * StaticSVTexture = NewObject < UStaticSparseVolumeTexture > ( InParent , UStaticSparseVolumeTexture : : StaticClass ( ) , NewName , Flags ) ;
2023-02-22 09:39:24 -05:00
ExpandVolumeBounds ( ImportOptions , PreviewData . GridInfo , VolumeBoundsMin , VolumeBoundsMax ) ;
StaticSVTexture - > VolumeResolution = VolumeBoundsMax - VolumeBoundsMin ;
2023-02-01 06:20:15 -05:00
StaticSVTexture - > Frames . SetNum ( 1 ) ;
2023-01-24 21:44:51 -05:00
2023-02-22 09:39:24 -05:00
const int32 NumMipLevels = ComputeNumMipLevels ( VolumeBoundsMin , VolumeBoundsMax ) ;
2022-11-25 09:18:31 -05:00
FSparseVolumeRawSource SparseVolumeRawSource { } ;
2023-02-22 09:39:24 -05:00
const bool bConversionSuccess = ConvertOpenVDBToSparseVolumeTexture ( PreviewData . LoadedFile , ImportOptions , VolumeBoundsMin , SparseVolumeRawSource ) ;
2023-01-31 01:11:48 -05:00
if ( ! bConversionSuccess )
{
UE_LOG ( LogSparseVolumeTextureFactory , Error , TEXT ( " Failed to convert OpenVDB file to SparseVolumeTexture: %s " ) , * Filename ) ;
return nullptr ;
}
2022-11-25 09:18:31 -05:00
// Serialize the raw source data into the asset object.
2023-02-22 09:39:24 -05:00
// After serializing the data, we generate the next mip level by downsampling the current one (with a call to GenerateMipMap())
// and then repeating the loop until we processed all levels.
StaticSVTexture - > Frames [ 0 ] . SetNum ( NumMipLevels ) ;
for ( int32 MipLevel = 0 ; MipLevel < NumMipLevels ; + + MipLevel )
2022-10-16 02:28:34 -04:00
{
2023-02-22 09:39:24 -05:00
UE : : Serialization : : FEditorBulkDataWriter RawDataArchiveWriter ( StaticSVTexture - > Frames [ 0 ] [ MipLevel ] . RawData ) ;
2022-11-25 09:18:31 -05:00
SparseVolumeRawSource . Serialize ( RawDataArchiveWriter ) ;
2023-02-22 09:39:24 -05:00
if ( ( MipLevel + 1 ) < NumMipLevels )
{
SparseVolumeRawSource = SparseVolumeRawSource . GenerateMipMap ( ) ;
}
2022-10-16 02:28:34 -04:00
}
if ( ImportTask . ShouldCancel ( ) )
{
2023-01-31 01:11:48 -05:00
bOutOperationCanceled = true ;
2022-10-16 02:28:34 -04:00
return nullptr ;
}
ImportTask . EnterProgressFrame ( 1.0f , LOCTEXT ( " ConvertingVDBStatic " , " Converting static OpenVDB " ) ) ;
ResultAssets . Add ( StaticSVTexture ) ;
}
2023-01-24 21:44:51 -05:00
else
{
// Import as an animated sparse volume texture asset.
// Data from original file is no longer needed; we iterate over all frames later
PreviewData . LoadedFile . Empty ( ) ;
FName NewName ( InName . ToString ( ) + TEXT ( " VDBAnim " ) ) ;
UAnimatedSparseVolumeTexture * AnimatedSVTexture = NewObject < UAnimatedSparseVolumeTexture > ( InParent , UAnimatedSparseVolumeTexture : : StaticClass ( ) , NewName , Flags ) ;
2023-01-30 11:48:00 -05:00
const int32 NumFrames = PreviewData . SequenceFilenames . Num ( ) ;
2023-01-24 21:44:51 -05:00
2023-02-22 09:39:24 -05:00
FScopedSlowTask ImportTask ( NumFrames + 1 , LOCTEXT ( " ImportingVDBAnim " , " Importing OpenVDB animation " ) ) ;
2023-01-24 21:44:51 -05:00
ImportTask . MakeDialog ( true ) ;
// Allocate space for each frame
2023-02-01 06:20:15 -05:00
AnimatedSVTexture - > Frames . SetNum ( NumFrames ) ;
2023-01-24 21:44:51 -05:00
2023-02-16 10:42:36 -05:00
std : : atomic_bool bErrored = false ;
std : : atomic_bool bCanceled = false ;
2023-02-22 09:39:24 -05:00
// Compute volume bounds and check sequence files for compatiblity
std : : mutex VolumeBoundsMutex ;
ParallelFor ( NumFrames , [ & bErrored , & VolumeBoundsMutex , & VolumeBoundsMin , & VolumeBoundsMax , & ExpandVolumeBounds , & PreviewData , & ImportOptions ] ( int32 FrameIdx )
{
if ( bErrored . load ( ) )
{
return ;
}
// Load file and get info about each contained grid
const FString & FrameFilename = PreviewData . SequenceFilenames [ FrameIdx ] ;
2023-02-23 14:53:04 -05:00
TArray64 < uint8 > LoadedFrameFile ;
2023-02-22 09:39:24 -05:00
if ( ! FFileHelper : : LoadFileToArray ( LoadedFrameFile , * FrameFilename ) )
{
UE_LOG ( LogSparseVolumeTextureFactory , Error , TEXT ( " OpenVDB file could not be loaded: %s " ) , * FrameFilename ) ;
bErrored . store ( true ) ;
return ;
}
TArray < FOpenVDBGridInfo > FrameGridInfo ;
if ( ! GetOpenVDBGridInfo ( LoadedFrameFile , true , & FrameGridInfo ) )
{
UE_LOG ( LogSparseVolumeTextureFactory , Error , TEXT ( " Failed to read OpenVDB file: %s " ) , * FrameFilename ) ;
bErrored . store ( true ) ;
return ;
}
// Sanity check for compatibility
for ( const FOpenVDBSparseVolumeAttributesDesc & AttributesDesc : ImportOptions . Attributes )
{
for ( const FOpenVDBSparseVolumeComponentMapping & Mapping : AttributesDesc . Mappings )
{
const uint32 SourceGridIndex = Mapping . SourceGridIndex ;
if ( SourceGridIndex ! = INDEX_NONE )
{
if ( ( int32 ) SourceGridIndex > = FrameGridInfo . Num ( ) )
{
UE_LOG ( LogSparseVolumeTextureFactory , Error , TEXT ( " OpenVDB file is incompatible with other frames in the sequence: %s " ) , * FrameFilename ) ;
bErrored . store ( true ) ;
return ;
}
const FOpenVDBGridInfo & OrigSourceGrid = PreviewData . GridInfo [ SourceGridIndex ] ;
const FOpenVDBGridInfo & FrameSourceGrid = FrameGridInfo [ SourceGridIndex ] ;
if ( OrigSourceGrid . Type ! = FrameSourceGrid . Type | | OrigSourceGrid . Name ! = FrameSourceGrid . Name )
{
UE_LOG ( LogSparseVolumeTextureFactory , Error , TEXT ( " OpenVDB file is incompatible with other frames in the sequence: %s " ) , * FrameFilename ) ;
bErrored . store ( true ) ;
return ;
}
}
}
}
// Update sequence volume bounds and increment ProcessedFramesCounter
{
std : : lock_guard < std : : mutex > Lock ( VolumeBoundsMutex ) ;
ExpandVolumeBounds ( ImportOptions , FrameGridInfo , VolumeBoundsMin , VolumeBoundsMax ) ;
}
} ) ;
if ( bErrored . load ( ) )
{
return nullptr ;
}
ImportTask . EnterProgressFrame ( 1.0f , LOCTEXT ( " ConvertingVDBAnim " , " Converting OpenVDB animation " ) ) ;
const int32 NumMipLevels = ComputeNumMipLevels ( VolumeBoundsMin , VolumeBoundsMax ) ;
FEvent * AllTasksFinishedEvent = FPlatformProcess : : GetSynchEventFromPool ( ) ;
2023-02-16 10:42:36 -05:00
std : : atomic_int FinishedTasksCounter = 0 ; // Will be incremented even if frame processing failed
std : : atomic_int ProcessedFramesCounter = 0 ;
2023-01-24 21:44:51 -05:00
2023-02-22 09:39:24 -05:00
// Load individual frames, process/convert them and append them to the resulting asset
2023-01-24 21:44:51 -05:00
for ( int32 FrameIdx = 0 ; FrameIdx < NumFrames ; + + FrameIdx )
{
2023-02-16 10:42:36 -05:00
// Increments the atomic counter when going out of scope. Triggers an event once the counter reaches a given value.
struct FScopedIncrementer
2023-01-24 21:44:51 -05:00
{
2023-02-16 10:42:36 -05:00
std : : atomic_int & Counter ;
int32 MaxValue ;
FEvent * Event ;
explicit FScopedIncrementer ( std : : atomic_int & InCounter , int32 InMaxValue , FEvent * InEvent )
: Counter ( InCounter ) , MaxValue ( InMaxValue ) , Event ( InEvent ) { }
~ FScopedIncrementer ( )
{
if ( ( Counter . fetch_add ( 1 ) + 1 ) = = MaxValue )
2023-01-24 21:44:51 -05:00
{
2023-02-16 10:42:36 -05:00
Event - > Trigger ( ) ;
2023-01-24 21:44:51 -05:00
}
}
2023-02-16 10:42:36 -05:00
} ;
2023-01-24 21:44:51 -05:00
2023-02-16 10:42:36 -05:00
AsyncTask ( ENamedThreads : : AnyNormalThreadNormalTask ,
2023-02-22 09:39:24 -05:00
[ FrameIdx , NumFrames , & PreviewData , & ImportOptions , & AnimatedSVTexture ,
AllTasksFinishedEvent , & bErrored , & bCanceled , & FinishedTasksCounter , & ProcessedFramesCounter , & VolumeBoundsMin , & NumMipLevels ] ( )
2023-02-16 10:42:36 -05:00
{
// Ensure the FinishedTasksCounter will be incremented in all cases
FScopedIncrementer Incremeter ( FinishedTasksCounter , NumFrames , AllTasksFinishedEvent ) ;
2023-02-01 06:20:15 -05:00
2023-02-16 10:42:36 -05:00
if ( bErrored . load ( ) | | bCanceled . load ( ) )
{
return ;
}
2023-01-31 01:11:48 -05:00
2023-02-16 10:42:36 -05:00
const FString & FrameFilename = PreviewData . SequenceFilenames [ FrameIdx ] ;
2023-01-31 01:11:48 -05:00
2023-02-16 10:42:36 -05:00
UE_LOG ( LogSparseVolumeTextureFactory , Display , TEXT ( " Loading OpenVDB sequence frame #%i %s. " ) , FrameIdx , * FrameFilename ) ;
2023-01-31 01:11:48 -05:00
2023-02-22 09:39:24 -05:00
// Load file
2023-02-23 14:53:04 -05:00
TArray64 < uint8 > LoadedFrameFile ;
2023-02-16 10:42:36 -05:00
if ( ! FFileHelper : : LoadFileToArray ( LoadedFrameFile , * FrameFilename ) )
{
UE_LOG ( LogSparseVolumeTextureFactory , Error , TEXT ( " OpenVDB file could not be loaded: %s " ) , * FrameFilename ) ;
bErrored . store ( true ) ;
return ;
}
FSparseVolumeRawSource SparseVolumeRawSource { } ;
2023-02-22 09:39:24 -05:00
const bool bConversionSuccess = ConvertOpenVDBToSparseVolumeTexture ( LoadedFrameFile , ImportOptions , VolumeBoundsMin , SparseVolumeRawSource ) ;
2023-02-16 10:42:36 -05:00
if ( ! bConversionSuccess )
{
UE_LOG ( LogSparseVolumeTextureFactory , Error , TEXT ( " Failed to convert OpenVDB file to SparseVolumeTexture: %s " ) , * FrameFilename ) ;
bErrored . store ( true ) ;
return ;
}
// Serialize the raw source data from this frame into the asset object.
2023-02-22 09:39:24 -05:00
// After serializing the data, we generate the next mip level by downsampling the current one (with a call to GenerateMipMap())
// and then repeating the loop until we processed all levels.
AnimatedSVTexture - > Frames [ FrameIdx ] . SetNum ( NumMipLevels ) ;
for ( int32 MipLevel = 0 ; MipLevel < NumMipLevels ; + + MipLevel )
2023-02-16 10:42:36 -05:00
{
2023-02-22 09:39:24 -05:00
UE : : Serialization : : FEditorBulkDataWriter RawDataArchiveWriter ( AnimatedSVTexture - > Frames [ FrameIdx ] [ MipLevel ] . RawData ) ;
2023-02-16 10:42:36 -05:00
SparseVolumeRawSource . Serialize ( RawDataArchiveWriter ) ;
2023-02-22 09:39:24 -05:00
if ( ( MipLevel + 1 ) < NumMipLevels )
{
SparseVolumeRawSource = SparseVolumeRawSource . GenerateMipMap ( ) ;
}
2023-02-16 10:42:36 -05:00
}
2023-02-22 09:39:24 -05:00
// Increment ProcessedFramesCounter
2023-02-16 10:42:36 -05:00
ProcessedFramesCounter . fetch_add ( 1 ) ;
} ) ;
}
// Wait for frames to be processed
{
int NumFinishedTasks = 0 ;
int NumProcessedFrames = 0 ;
while ( NumFinishedTasks < NumFrames )
2023-01-31 01:11:48 -05:00
{
2023-02-16 10:42:36 -05:00
// We can't block here because we want to regularly update the progress bar and check for user input.
const uint32 WaitTimeMS = 2 ;
AllTasksFinishedEvent - > Wait ( WaitTimeMS ) ;
2023-01-24 21:44:51 -05:00
2023-02-16 10:42:36 -05:00
if ( ! bCanceled . load ( ) & & ! bErrored . load ( ) & & ImportTask . ShouldCancel ( ) )
{
bCanceled . store ( true ) ;
}
2023-01-24 21:44:51 -05:00
2023-02-16 10:42:36 -05:00
const int NewNumFinishedTasks = FinishedTasksCounter . load ( ) ;
if ( NewNumFinishedTasks > NumFinishedTasks )
{
const int NewNumProcessedFrames = ProcessedFramesCounter . load ( ) ;
if ( NewNumProcessedFrames > NumProcessedFrames & & ! bErrored . load ( ) )
{
const float Progress = float ( NewNumProcessedFrames - NumProcessedFrames ) ;
ImportTask . EnterProgressFrame ( Progress , LOCTEXT ( " ConvertingVDBAnim " , " Converting OpenVDB animation " ) ) ;
}
NumFinishedTasks = NewNumFinishedTasks ;
NumProcessedFrames = NewNumProcessedFrames ;
}
2023-01-24 21:44:51 -05:00
}
2023-02-16 10:42:36 -05:00
}
FPlatformProcess : : ReturnSynchEventToPool ( AllTasksFinishedEvent ) ;
if ( bCanceled . load ( ) )
{
bOutOperationCanceled = true ;
return nullptr ;
}
if ( bErrored . load ( ) )
{
return nullptr ;
2023-01-24 21:44:51 -05:00
}
2023-02-22 09:39:24 -05:00
AnimatedSVTexture - > VolumeResolution = VolumeBoundsMax - VolumeBoundsMin ;
2023-01-24 21:44:51 -05:00
ResultAssets . Add ( AnimatedSVTexture ) ;
}
2022-10-16 02:28:34 -04:00
// Now notify the system about the imported/updated/created assets
2023-02-07 08:59:18 -05:00
check ( ResultAssets . Num ( ) = = 1 ) ;
GEditor - > GetEditorSubsystem < UImportSubsystem > ( ) - > BroadcastAssetPostImport ( this , ResultAssets [ 0 ] ) ;
2022-10-16 02:28:34 -04:00
2023-02-07 08:59:18 -05:00
return ResultAssets [ 0 ] ;
2022-10-16 02:28:34 -04:00
# else // OPENVDB_AVAILABLE
// SVT_TODO Make sure we can also import on more platforms such as Linux. See SparseVolumeTextureOpenVDB.h
UE_LOG ( LogSparseVolumeTextureFactory , Error , TEXT ( " Cannot import OpenVDB asset any platform other than Windows. " ) ) ;
return nullptr ;
# endif // OPENVDB_AVAILABLE
}
# endif // WITH_EDITORONLY_DATA
# undef LOCTEXT_NAMESPACE
# include "Serialization/EditorBulkDataWriter.h"