Files
UnrealEngineUWP/Engine/Source/Developer/SimplygonMeshReduction/Private/SimplygonMeshReduction.cpp
Dmitriy Dyomin c89e35b8a5 Fixed metallic, roughness, specular color data extraction from simplygon
[CL 2476657 by Dmitriy Dyomin in Main branch]
2015-03-12 07:14:41 -04:00

1725 lines
66 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "UnrealEd.h"
#include "RawMesh.h"
#include "MeshUtilities.h"
// Standard Simplygon channels have some issues with extracting color data back from simplification,
// so we use this workaround with user channels
static const char* USER_MATERIAL_CHANNEL_METALLIC = "UserMetallic";
static const char* USER_MATERIAL_CHANNEL_ROUGHNESS = "UserRoughness";
static const char* USER_MATERIAL_CHANNEL_SPECULAR = "UserSpecular";
#ifdef __clang__
// SimplygonSDK.h uses 'deprecated' pragma which Clang does not recognize
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning : unknown pragma ignored [-Wunknown-pragmas]
#endif
#include "SimplygonSDK.h"
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#include "MeshBoneReduction.h"
#include "MaterialExportUtils.h"
#include "ComponentReregisterContext.h"
#define LOCTEXT_NAMESPACE "SimplygonMeshReduction"
class FSimplygonMeshReductionModule : public IMeshReductionModule
{
public:
// IModuleInterface interface.
virtual void StartupModule() override;
virtual void ShutdownModule() override;
// IMeshReductionModule interface.
virtual class IMeshReduction* GetMeshReductionInterface() override;
virtual class IMeshMerging* GetMeshMergingInterface() override;
};
DEFINE_LOG_CATEGORY_STATIC(LogSimplygon, Log, All);
IMPLEMENT_MODULE(FSimplygonMeshReductionModule, SimplygonMeshReduction);
#define SIMPLYGON_COLOR_CHANNEL "VertexColors"
/** Receives error messages generated by Simplygon. These errors are presented via a message box. */
class FDefaultErrorHandler : public SimplygonSDK::rerrorhandler
{
public :
virtual void HandleError(
SimplygonSDK::IObject* Object,
const char* InterfaceName,
const char* MethodName,
SimplygonSDK::rid ErrorType,
const char* ErrorText )
{
FString ErrorString = FString::Printf(TEXT("Simplygon Error:\n\nInterface: %s\nMethod: %s\nError: (%d) %s"),
ANSI_TO_TCHAR(InterfaceName),
ANSI_TO_TCHAR(MethodName),
ErrorType,
ANSI_TO_TCHAR(ErrorText)
);
UE_LOG(LogSimplygon, Log, TEXT("%s"), *ErrorString);
//FMessageDialog::Open(EAppMsgType::Ok, *ErrorString);
}
};
/** Receives progress events from Simplygon and updates the status window. */
class FDefaultEventHandler : public SimplygonSDK::robserver
{
public:
virtual void Execute(
SimplygonSDK::IObject* Object,
SimplygonSDK::rid EventId,
void* EventParameterBlock,
unsigned int EventParameterBlockSize )
{
if ( EventId == SimplygonSDK::SG_EVENT_PROGRESS )
{
check( sizeof(int32) == EventParameterBlockSize );
int32 ProgressPercent = *((int32*)EventParameterBlock);
GWarn->UpdateProgress( ProgressPercent, 100 );
// We are required to pass '1' back through the EventParametersBlock for the process to continue.
*((int32*)EventParameterBlock) = 1;
}
}
};
/** Winding modes. */
enum EWindingMode
{
/** Maintain the winding of the mesh. */
WINDING_Keep,
/** Reverse the winding of the mesh. */
WINDING_Reverse,
WINDING_MAX
};
class FSimplygonMeshReduction
: public IMeshReduction
, public IMeshMerging
{
public:
virtual ~FSimplygonMeshReduction()
{
}
virtual const FString& GetVersionString() const override
{
return VersionString;
}
virtual void Reduce(
FRawMesh& OutReducedMesh,
float& OutMaxDeviation,
const FRawMesh& InMesh,
const FMeshReductionSettings& InSettings
) override
{
SimplygonSDK::spGeometryData GeometryData = CreateGeometryFromRawMesh(InMesh);
check(GeometryData);
SimplygonSDK::spReductionProcessor ReductionProcessor = SDK->CreateReductionProcessor();
ReductionProcessor->AddObserver(&EventHandler, SimplygonSDK::SG_EVENT_PROGRESS);
ReductionProcessor->SetGeometry(GeometryData);
SimplygonSDK::spRepairSettings RepairSettings = ReductionProcessor->GetRepairSettings();
RepairSettings->SetWeldDist(InSettings.WeldingThreshold);
RepairSettings->SetTjuncDist(InSettings.WeldingThreshold);
SimplygonSDK::spReductionSettings ReductionSettings = ReductionProcessor->GetReductionSettings();
SetReductionSettings(ReductionSettings, InSettings, GeometryData->GetTriangleCount());
SimplygonSDK::spNormalCalculationSettings NormalSettings = ReductionProcessor->GetNormalCalculationSettings();
SetNormalSettings(NormalSettings, InSettings);
ReductionProcessor->RunProcessing();
CreateRawMeshFromGeometry(OutReducedMesh, GeometryData, WINDING_Keep);
OutMaxDeviation = ReductionProcessor->GetMaxDeviation();
}
bool ReduceLODModel(
FStaticLODModel * SrcModel,
FStaticLODModel *& OutModel,
FBoxSphereBounds & Bounds,
float &MaxDeviation,
const FReferenceSkeleton& RefSkeleton,
const FSkeletalMeshOptimizationSettings& Settings
)
{
const bool bUsingMaxDeviation = (Settings.ReductionMethod == SMOT_MaxDeviation && Settings.MaxDeviationPercentage > 0.0f);
const bool bUsingReductionRatio = (Settings.ReductionMethod == SMOT_NumOfTriangles && Settings.NumOfTrianglesPercentage < 1.0f);
const bool bProcessGeometry = ( bUsingMaxDeviation || bUsingReductionRatio );
const bool bProcessBones = (Settings.BoneReductionRatio < 1.0f || Settings.MaxBonesPerVertex < MAX_TOTAL_INFLUENCES);
const bool bOptimizeMesh = (bProcessGeometry || bProcessBones);
// We'll need to store the max deviation after optimization if we wish to recalculate the LOD's display distance
MaxDeviation = 0.0f;
if ( bOptimizeMesh )
{
//Create a simplygon scene. The scene by default contains a bone table that is required for bone reduction.
SimplygonSDK::spScene Scene = SDK->CreateScene();
check( Scene );
// Create bone hierarchy for simplygon.
TArray<SimplygonSDK::rid> BoneTableIDs;
CreateSkeletalHierarchy(Scene, RefSkeleton, BoneTableIDs);
// Create a new scene mesh object
SimplygonSDK::spGeometryData GeometryData = CreateGeometryFromSkeletalLODModel( Scene, *SrcModel, BoneTableIDs );
FDefaultEventHandler SimplygonEventHandler;
SimplygonSDK::spReductionProcessor ReductionProcessor = SDK->CreateReductionProcessor();
ReductionProcessor->AddObserver( &EventHandler, SimplygonSDK::SG_EVENT_PROGRESS );
ReductionProcessor->SetSceneRoot(Scene->GetRootNode());
SimplygonSDK::spRepairSettings RepairSettings = ReductionProcessor->GetRepairSettings();
RepairSettings->SetWeldDist( Settings.WeldingThreshold );
RepairSettings->SetTjuncDist( Settings.WeldingThreshold );
SimplygonSDK::spReductionSettings ReductionSettings = ReductionProcessor->GetReductionSettings();
SetReductionSettings( Settings, Bounds.SphereRadius, GeometryData->GetTriangleCount(), ReductionSettings );
SimplygonSDK::spNormalCalculationSettings NormalSettings = ReductionProcessor->GetNormalCalculationSettings();
SetNormalSettings( Settings, NormalSettings );
if(bProcessBones)
{
SimplygonSDK::spBoneSettings BoneSettings = ReductionProcessor->GetBoneSettings();
SetBoneSettings( Settings, BoneSettings );
}
ReductionProcessor->RunProcessing();
// We require the max deviation if we're calculating the LOD's display distance.
MaxDeviation = ReductionProcessor->GetMaxDeviation();
CreateSkeletalLODModelFromGeometry( GeometryData, RefSkeleton, OutModel );
}
return bOptimizeMesh;
}
virtual bool ReduceSkeletalMesh(
USkeletalMesh* SkeletalMesh,
int32 LODIndex,
const FSkeletalMeshOptimizationSettings& Settings,
bool bCalcLODDistance
) override
{
check( SkeletalMesh );
check( LODIndex >= 0 );
check( LODIndex <= SkeletalMesh->LODInfo.Num() );
FSkeletalMeshResource* SkeletalMeshResource = SkeletalMesh->GetImportedResource();
check(SkeletalMeshResource);
check( LODIndex <= SkeletalMeshResource->LODModels.Num() );
FStaticLODModel* SrcModel = &SkeletalMesh->PreModifyMesh();
TComponentReregisterContext<USkinnedMeshComponent> ReregisterContext;
SkeletalMesh->ReleaseResources();
SkeletalMesh->ReleaseResourcesFence.Wait();
// Insert a new LOD model entry if needed.
if ( LODIndex == SkeletalMeshResource->LODModels.Num() )
{
SkeletalMeshResource->LODModels.Add(0);
}
// We'll need to store the max deviation after optimization if we wish to recalculate the LOD's display distance
float MaxDeviation = 0.0f;
// Swap in a new model, delete the old.
check( LODIndex < SkeletalMeshResource->LODModels.Num() );
FStaticLODModel** LODModels = SkeletalMeshResource->LODModels.GetData();
delete LODModels[LODIndex];
// Copy over LOD info from LOD0 if there is no previous info.
if ( LODIndex == SkeletalMesh->LODInfo.Num() )
{
FSkeletalMeshLODInfo* NewLODInfo = new( SkeletalMesh->LODInfo ) FSkeletalMeshLODInfo;
FSkeletalMeshLODInfo& OldLODInfo = SkeletalMesh->LODInfo[0];
*NewLODInfo = OldLODInfo;
// creates LOD Material map for a newly generated LOD model
int32 NumSections = LODModels[0]->NumNonClothingSections();
if (NewLODInfo->LODMaterialMap.Num() != NumSections)
{
NewLODInfo->LODMaterialMap.Empty(NumSections);
NewLODInfo->LODMaterialMap.AddUninitialized(NumSections);
}
for (int32 Index = 0; Index < NumSections; Index++)
{
NewLODInfo->LODMaterialMap[Index] = Index;
}
}
// now try bone reduction process if it's setup
TMap<FBoneIndexType, FBoneIndexType> BonesToRemove;
IMeshBoneReductionModule& MeshBoneReductionModule = FModuleManager::Get().LoadModuleChecked<IMeshBoneReductionModule>("MeshBoneReduction");
IMeshBoneReduction * MeshBoneReductionInterface = MeshBoneReductionModule.GetMeshBoneReductionInterface();
// See if we'd like to remove extra bones first
if (MeshBoneReductionInterface->GetBoneReductionData( SkeletalMesh, LODIndex, BonesToRemove ))
{
// if we do, now create new model and make a copy of SrcMesh to cut the bone count
FStaticLODModel * NewSrcModel = new FStaticLODModel();
// Bulk data arrays need to be locked before a copy can be made.
SrcModel->RawPointIndices.Lock( LOCK_READ_ONLY );
SrcModel->LegacyRawPointIndices.Lock( LOCK_READ_ONLY );
*NewSrcModel = *SrcModel;
SrcModel->RawPointIndices.Unlock();
SrcModel->LegacyRawPointIndices.Unlock();
// The index buffer needs to be rebuilt on copy.
FMultiSizeIndexContainerData IndexBufferData;
SrcModel->MultiSizeIndexContainer.GetIndexBufferData( IndexBufferData );
NewSrcModel->MultiSizeIndexContainer.RebuildIndexBuffer( IndexBufferData );
// now fix up SrcModel to NewSrcModel
SrcModel = NewSrcModel;
// fix up chunks to remove the bones that set to be removed
for ( int32 ChunkIndex=0; ChunkIndex< NewSrcModel->Chunks.Num(); ++ChunkIndex )
{
MeshBoneReductionInterface->FixUpChunkBoneMaps(NewSrcModel->Chunks[ChunkIndex], BonesToRemove);
}
}
FStaticLODModel * NewModel = new FStaticLODModel();
LODModels[LODIndex] = NewModel;
// Reduce LOD model with SrcMesh
if ( ReduceLODModel(SrcModel, NewModel, SkeletalMesh->Bounds, MaxDeviation, SkeletalMesh->RefSkeleton, Settings) )
{
if( bCalcLODDistance )
{
float ViewDistance = CalculateViewDistance( MaxDeviation );
SkeletalMesh->LODInfo[LODIndex].ScreenSize = 2.0f * SkeletalMesh->Bounds.SphereRadius / ViewDistance;
}
// Flag this LOD as having been simplified.
SkeletalMesh->LODInfo[LODIndex].bHasBeenSimplified = true;
SkeletalMesh->bHasBeenSimplified = true;
}
else
{
// Bulk data arrays need to be locked before a copy can be made.
SrcModel->RawPointIndices.Lock( LOCK_READ_ONLY );
SrcModel->LegacyRawPointIndices.Lock( LOCK_READ_ONLY );
*NewModel = *SrcModel;
SrcModel->RawPointIndices.Unlock();
SrcModel->LegacyRawPointIndices.Unlock();
// The index buffer needs to be rebuilt on copy.
FMultiSizeIndexContainerData IndexBufferData;
SrcModel->MultiSizeIndexContainer.GetIndexBufferData( IndexBufferData );
NewModel->MultiSizeIndexContainer.RebuildIndexBuffer( IndexBufferData );
// Required bones are recalculated later on.
NewModel->RequiredBones.Empty();
SkeletalMesh->LODInfo[LODIndex].bHasBeenSimplified = false;
}
SkeletalMesh->CalculateRequiredBones( SkeletalMeshResource->LODModels[LODIndex], SkeletalMesh->RefSkeleton, &BonesToRemove );
SkeletalMesh->PostEditChange();
SkeletalMesh->InitResources();
if ( LODIndex >= SkeletalMesh->OptimizationSettings.Num() )
{
FSkeletalMeshOptimizationSettings DefaultSettings;
const FSkeletalMeshOptimizationSettings SettingsToCopy =
SkeletalMesh->OptimizationSettings.Num() ? SkeletalMesh->OptimizationSettings.Last() : DefaultSettings;
while ( LODIndex >= SkeletalMesh->OptimizationSettings.Num() )
{
SkeletalMesh->OptimizationSettings.Add( SettingsToCopy );
}
}
check( LODIndex < SkeletalMesh->OptimizationSettings.Num() );
SkeletalMesh->OptimizationSettings[ LODIndex ] = Settings;
return true;
}
virtual bool IsSupported() const override
{
return true;
}
static FSimplygonMeshReduction* Create()
{
FString DllPath(FPaths::Combine(*FPaths::EngineDir(), TEXT("Binaries/ThirdParty/NotForLicensees/Simplygon")));
FString DllFilename(FPaths::Combine(*DllPath, TEXT("SimplygonSDKEpicUE4Releasex64.dll")));
if( !FPaths::FileExists(DllFilename) )
{
DllFilename = FPaths::Combine(*DllPath, TEXT("SimplygonSDKRuntimeReleasex64.dll"));
}
// If the DLL just doesn't exist, fail gracefully. Licensees and Rocket users will not necessarily have Simplygon.
if( !FPaths::FileExists(DllFilename) )
{
UE_LOG(LogSimplygon,Warning,TEXT("Simplygon DLL not present - disabling."));
return NULL;
}
// Otherwise fail
void* DLLHandle = FPlatformProcess::GetDllHandle(*DllFilename);
if (DLLHandle == NULL)
{
int32 ErrorNum = FPlatformMisc::GetLastError();
TCHAR ErrorMsg[1024];
FPlatformMisc::GetSystemErrorMessage( ErrorMsg, 1024, ErrorNum );
UE_LOG(LogSimplygon,Error,TEXT("Failed to get Simplygon DLL handle: %s (%d)"),ErrorMsg,ErrorNum);
return NULL;
}
// Get API function pointers of interest
typedef void (*GetInterfaceVersionSimplygonSDKPtr)(ANSICHAR*);
GetInterfaceVersionSimplygonSDKPtr GetInterfaceVersionSimplygonSDK = (GetInterfaceVersionSimplygonSDKPtr)FPlatformProcess::GetDllExport( DLLHandle, TEXT( "GetInterfaceVersionSimplygonSDK" ) );
typedef int (*InitializeSimplygonSDKPtr)(const char* LicenseData , SimplygonSDK::ISimplygonSDK** OutInterfacePtr);
InitializeSimplygonSDKPtr InitializeSimplygonSDK = (InitializeSimplygonSDKPtr)FPlatformProcess::GetDllExport( DLLHandle, TEXT( "InitializeSimplygonSDK" ) );
if ((GetInterfaceVersionSimplygonSDK == NULL) || (InitializeSimplygonSDK == NULL))
{
// Couldn't find the functions we need.
UE_LOG(LogSimplygon,Warning,TEXT("Failed to acquire Simplygon DLL exports."));
FPlatformProcess::FreeDllHandle( DLLHandle );
return NULL;
}
ANSICHAR VersionHash[200];
GetInterfaceVersionSimplygonSDK(VersionHash);
if (FCStringAnsi::Strcmp(VersionHash, SimplygonSDK::GetInterfaceVersionHash()) != 0)
{
UE_LOG(LogSimplygon,Warning,TEXT("Library version mismatch. Header=%s Lib=%s"),ANSI_TO_TCHAR(SimplygonSDK::GetInterfaceVersionHash()),ANSI_TO_TCHAR(VersionHash));
return NULL;
}
const char* LicenseData = NULL;
TArray<uint8> LicenseFileContents;
if (FFileHelper::LoadFileToArray(LicenseFileContents, *FPaths::Combine(*DllPath, TEXT("license.dat")), FILEREAD_Silent) && LicenseFileContents.Num() > 0)
{
LicenseData = (const char*)LicenseFileContents.GetData();
}
SimplygonSDK::ISimplygonSDK* SDK = NULL;
int32 Result = InitializeSimplygonSDK(LicenseData, &SDK);
if (Result != SimplygonSDK::SG_ERROR_NOERROR && Result != SimplygonSDK::SG_ERROR_ALREADYINITIALIZED)
{
UE_LOG(LogSimplygon,Warning,TEXT("Failed to initialize Simplygon. Error: %d."),Result);
SDK = NULL;
return NULL;
}
return new FSimplygonMeshReduction(SDK);
}
struct FMaterialCastingProperties
{
bool bCastMaterials;
bool bCastNormals;
bool bCastMetallic;
bool bCastRoughness;
bool bCastSpecular;
FMaterialCastingProperties()
: bCastMaterials(false)
, bCastNormals(false)
, bCastMetallic(false)
, bCastRoughness(false)
, bCastSpecular(false)
{
}
};
// IMeshMerging interface
virtual void BuildProxy(
const TArray<FRawMesh>& InputMeshes,
const TArray<MaterialExportUtils::FFlattenMaterial>& InputMaterials,
const struct FMeshProxySettings& InProxySettings,
FRawMesh& OutProxyMesh,
MaterialExportUtils::FFlattenMaterial& OutMaterial) override
{
if (InputMeshes.Num() == 0)
{
return;
}
//Create a Simplygon Scene
SimplygonSDK::spScene Scene = SDK->CreateScene();
//Material table for the original materials
SimplygonSDK::spMaterialTable OriginalMaterials = Scene->GetMaterialTable();
//Build simplygon materials from the original asset materials
FMaterialCastingProperties CastProperties;
CastProperties.bCastMaterials = CreateSGMaterialFromFlattenMaterial(InputMaterials, OriginalMaterials, CastProperties);
//For each raw mesh in array create a scene mesh and populate with geometry data
for(int32 MeshIndex = 0; MeshIndex < InputMeshes.Num() ; ++MeshIndex)
{
SimplygonSDK::spSceneMesh Mesh = SDK->CreateSceneMesh();
SimplygonSDK::spGeometryData GeometryData = CreateGeometryFromRawMesh(InputMeshes[MeshIndex]);
check(GeometryData)
#ifdef DEBUG_PROXY_MESH
SimplygonSDK::spWavefrontExporter objexp = SDK->CreateWavefrontExporter();
objexp->SetExportFilePath( "d:/BeforeProxyMesh.obj" );
objexp->SetSingleGeometry( GeometryData);
objexp->RunExport();
#endif
Mesh->SetGeometry(GeometryData);
Mesh->SetName(TCHAR_TO_ANSI(*FString::Printf(TEXT("Mesh%d"),MeshIndex)));
Scene->GetRootNode()->AddChild(Mesh);
}
//Create a remesher
SimplygonSDK::spRemeshingProcessor RemeshingProcessor = SDK->CreateRemeshingProcessor();
//Setup the remesher
RemeshingProcessor->AddObserver(&EventHandler, SimplygonSDK::SG_EVENT_PROGRESS);
RemeshingProcessor->GetRemeshingSettings()->SetOnScreenSize(InProxySettings.ScreenSize);
RemeshingProcessor->GetRemeshingSettings()->SetMergeDistance(InProxySettings.MergeDistance);
RemeshingProcessor->GetRemeshingSettings()->SetUseGroundPlane(InProxySettings.bUseClippingPlane);
RemeshingProcessor->GetRemeshingSettings()->SetGroundPlaneAxisIndex(InProxySettings.AxisIndex); // 0:X-Axis, 1:Y-Axis, 2:Z-Axis
RemeshingProcessor->GetRemeshingSettings()->SetGroundPlaneLevel(InProxySettings.ClippingLevel);
// Goal is: If user specifies negative clipping plane -> negative halfspace should be clipped
if (InProxySettings.AxisIndex <= 1) // Invert X and Y axis
{
RemeshingProcessor->GetRemeshingSettings()->SetGroundPlaneNegativeHalfspace(!InProxySettings.bPlaneNegativeHalfspace);
}
else
{
RemeshingProcessor->GetRemeshingSettings()->SetGroundPlaneNegativeHalfspace(InProxySettings.bPlaneNegativeHalfspace);
}
RemeshingProcessor->GetRemeshingSettings()->SetTransferNormals( false );
RemeshingProcessor->GetRemeshingSettings()->SetMergeDistance( InProxySettings.MergeDistance );
RemeshingProcessor->GetRemeshingSettings()->SetHardEdgeAngle( FMath::DegreesToRadians(InProxySettings.HardAngleThreshold) );//This should be a user settings in the popup dialog!
RemeshingProcessor->SetSceneRoot(Scene->GetRootNode());
//Setup the mapping image used for casting
SimplygonSDK::spMappingImageSettings MappingSettings = RemeshingProcessor->GetMappingImageSettings();
MappingSettings->SetGenerateMappingImage( true );
MappingSettings->SetGenerateTexCoords( true );
MappingSettings->SetGenerateTangents( false );
MappingSettings->SetWidth( InProxySettings.TextureWidth );
MappingSettings->SetHeight( InProxySettings.TextureHeight );
//Start remeshing the geometry
RemeshingProcessor->RemeshGeometry();
if(CastProperties.bCastMaterials)
{
//Collect the mapping image from the remeshing process
SimplygonSDK::spMappingImage MappingImage = RemeshingProcessor->GetMappingImage();
//Create a new material for the proxy geometry
SimplygonSDK::spMaterial OutputMaterialLOD = SDK->CreateMaterial();
//Cast diffuse texture data
{
//Create Image data where the diffuse data is stored
SimplygonSDK::spImageData OutputDiffuseData = SDK->CreateImageData();
// Cast the data using a color caster
SimplygonSDK::spColorCaster cast = SDK->CreateColorCaster();
cast->SetColorType( SimplygonSDK::SG_MATERIAL_CHANNEL_DIFFUSE );
cast->SetSourceMaterials( OriginalMaterials );
cast->SetMappingImage( MappingImage ); // The mapping image we got from the remeshing process.
cast->SetOutputChannels( 4 ); // RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.)
cast->SetOutputChannelBitDepth( 8 ); // 8 bits per channel. So in this case we will have 24bit colors RGB.
cast->SetDilation( 10 ); // To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree aswell.
cast->SetOutputImage(OutputDiffuseData);
cast->CastMaterials(); // Fetch!
// set the material properties
// Set the diffuse multiplier for the texture. 1 means it will not differ from original texture,
// For example: 0 would ignore a specified color and 2 would make a color twice as pronounced as the others.
OutputMaterialLOD->SetDiffuseRed(1);
OutputMaterialLOD->SetDiffuseGreen(1);
OutputMaterialLOD->SetDiffuseBlue(1);
OutputMaterialLOD->SetLayeredTextureImage(SimplygonSDK::SG_MATERIAL_CHANNEL_DIFFUSE, 0, OutputDiffuseData);
OutputMaterialLOD->SetLayeredTextureLevel(SimplygonSDK::SG_MATERIAL_CHANNEL_DIFFUSE,0,0);
}
// Normal texture data
if(CastProperties.bCastNormals)
{
//Create Image data where the normal data is stored
SimplygonSDK::spImageData OutputNormalData = SDK->CreateImageData();
// Cast the data using a normal caster
SimplygonSDK::spNormalCaster cast = SDK->CreateNormalCaster();
cast->SetSourceMaterials( OriginalMaterials );
cast->SetMappingImage( MappingImage ); // The mapping image we got from the remeshing process.
cast->SetOutputChannels( 3 ); // RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.)
cast->SetOutputChannelBitDepth( 8 ); // 8 bits per channel. So in this case we will have 24bit colors RGB.
cast->SetDilation( 10 ); // To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree aswell.
cast->SetFlipBackfacingNormals(false);
cast->SetGenerateTangentSpaceNormals(true);
cast->SetOutputImage(OutputNormalData);
cast->CastMaterials(); // Fetch!
OutputMaterialLOD->SetLayeredTextureImage(SimplygonSDK::SG_MATERIAL_CHANNEL_NORMALS, 0, OutputNormalData);
OutputMaterialLOD->SetLayeredTextureLevel(SimplygonSDK::SG_MATERIAL_CHANNEL_NORMALS, 0, 0);
}
// Metallic texture data
if(CastProperties.bCastMetallic)
{
// Create Image data where the metallic data is stored
SimplygonSDK::spImageData OutputMetallicData = SDK->CreateImageData();
// Cast the data using a color caster
SimplygonSDK::spColorCaster cast = SDK->CreateColorCaster();
cast->SetColorType( USER_MATERIAL_CHANNEL_METALLIC );
cast->SetSourceMaterials( OriginalMaterials );
cast->SetMappingImage( MappingImage );
cast->SetOutputChannels( 3 );
cast->SetOutputChannelBitDepth( 8 );
cast->SetsRGB(false);
cast->SetDilation( 10 );
cast->SetOutputImage(OutputMetallicData);
cast->CastMaterials(); // Fetch!
OutputMaterialLOD->AddUserChannel(USER_MATERIAL_CHANNEL_METALLIC);
OutputMaterialLOD->SetLayeredTextureImage(USER_MATERIAL_CHANNEL_METALLIC, 0, OutputMetallicData);
OutputMaterialLOD->SetLayeredTextureLevel(USER_MATERIAL_CHANNEL_METALLIC, 0, 0);
}
// Roughness texture data
if(CastProperties.bCastRoughness)
{
// Create Image data where the metallic data is stored
SimplygonSDK::spImageData OutputRoughnessData = SDK->CreateImageData();
// Cast the data using a color caster
SimplygonSDK::spColorCaster cast = SDK->CreateColorCaster();
cast->SetColorType( USER_MATERIAL_CHANNEL_ROUGHNESS );
cast->SetSourceMaterials( OriginalMaterials );
cast->SetMappingImage( MappingImage );
cast->SetOutputChannels( 3 );
cast->SetOutputChannelBitDepth( 8 );
cast->SetsRGB(false);
cast->SetDilation( 10 );
cast->SetOutputImage(OutputRoughnessData);
cast->CastMaterials(); // Fetch!
OutputMaterialLOD->AddUserChannel(USER_MATERIAL_CHANNEL_ROUGHNESS);
OutputMaterialLOD->SetLayeredTextureImage(USER_MATERIAL_CHANNEL_ROUGHNESS, 0, OutputRoughnessData);
OutputMaterialLOD->SetLayeredTextureLevel(USER_MATERIAL_CHANNEL_ROUGHNESS, 0, 0);
}
// Specular texture data
if(CastProperties.bCastSpecular)
{
// Create Image data where the metallic data is stored
SimplygonSDK::spImageData OutputSpecularData = SDK->CreateImageData();
// Cast the data using a color caster
SimplygonSDK::spColorCaster cast = SDK->CreateColorCaster();
cast->SetColorType( USER_MATERIAL_CHANNEL_SPECULAR );
cast->SetSourceMaterials( OriginalMaterials );
cast->SetMappingImage( MappingImage );
cast->SetOutputChannels( 3 );
cast->SetOutputChannelBitDepth( 8 );
cast->SetsRGB(false);
cast->SetDilation( 10 );
cast->SetOutputImage(OutputSpecularData);
cast->CastMaterials(); // Fetch!
OutputMaterialLOD->AddUserChannel(USER_MATERIAL_CHANNEL_SPECULAR);
OutputMaterialLOD->SetLayeredTextureImage(USER_MATERIAL_CHANNEL_SPECULAR, 0, OutputSpecularData);
OutputMaterialLOD->SetLayeredTextureLevel(USER_MATERIAL_CHANNEL_SPECULAR, 0, 0);
}
//Create a new material table for the new materials
SimplygonSDK::spMaterialTable OutputTable = SDK->CreateMaterialTable();
OutputTable->AddMaterial(OutputMaterialLOD);
//Convert the simplygon material to unreal materials
CreateFlattenMaterialFromSGMaterial(OutputTable, OutMaterial);
}
//Collect the proxy mesh
SimplygonSDK::spSceneMesh ProxyMesh = SimplygonSDK::Cast<SimplygonSDK::ISceneMesh>(Scene->GetRootNode()->GetChild(0));
#ifdef DEBUG_PROXY_MESH
SimplygonSDK::spWavefrontExporter objexp = SDK->CreateWavefrontExporter();
objexp->SetExportFilePath( "d:/AfterProxyMesh.obj" );
objexp->SetSingleGeometry( ProxyMesh->GetGeometry() );
objexp->RunExport();
#endif
//Convert geometry data to raw mesh data
SimplygonSDK::spGeometryData outGeom = ProxyMesh->GetGeometry();
CreateRawMeshFromGeometry(OutProxyMesh, ProxyMesh->GetGeometry(), WINDING_Keep);
// Since mesh proxies have 1 texture channel
// put copy to channel 1 for lightmaps
OutProxyMesh.WedgeTexCoords[1].Empty();
OutProxyMesh.WedgeTexCoords[1].Append(OutProxyMesh.WedgeTexCoords[0]);
// Default smoothing
OutProxyMesh.FaceSmoothingMasks.SetNum(OutProxyMesh.FaceMaterialIndices.Num());
for (uint32& SmoothingMask : OutProxyMesh.FaceSmoothingMasks)
{
SmoothingMask = 1;
}
}
private:
SimplygonSDK::ISimplygonSDK* SDK;
FDefaultErrorHandler ErrorHandler;
FDefaultEventHandler EventHandler;
FString VersionString;
explicit FSimplygonMeshReduction(SimplygonSDK::ISimplygonSDK* InSDK)
: SDK(InSDK)
{
check(SDK);
SDK->SetErrorHandler(&ErrorHandler);
SDK->SetGlobalSetting("DefaultTBNType", SimplygonSDK::SG_TANGENTSPACEMETHOD_ORTHONORMAL_LEFTHANDED);
const TCHAR* LibraryVersion = TEXT("Simplygon_5_5_2156");
const TCHAR* UnrealVersionGuid = TEXT("18f808c3cf724e5a994f57de5c83cc4b");
VersionString = FString::Printf(TEXT("%s_%s"), LibraryVersion, UnrealVersionGuid);
}
SimplygonSDK::spGeometryData CreateGeometryFromRawMesh(const FRawMesh& RawMesh)
{
int32 NumVertices = RawMesh.VertexPositions.Num();
int32 NumWedges = RawMesh.WedgeIndices.Num();
int32 NumTris = NumWedges / 3;
if (NumWedges == 0)
{
return NULL;
}
SimplygonSDK::spGeometryData GeometryData = SDK->CreateGeometryData();
GeometryData->SetVertexCount(NumVertices);
GeometryData->SetTriangleCount(NumTris);
SimplygonSDK::spRealArray Positions = GeometryData->GetCoords();
for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex)
{
FVector TempPos = RawMesh.VertexPositions[VertexIndex];
TempPos.Z = -TempPos.Z;
Positions->SetTuple(VertexIndex, (float*)&TempPos);
}
SimplygonSDK::spRidArray Indices = GeometryData->GetVertexIds();
for (int32 WedgeIndex = 0; WedgeIndex < NumWedges; ++WedgeIndex)
{
Indices->SetItem(WedgeIndex, RawMesh.WedgeIndices[WedgeIndex]);
}
for(int32 TexCoordIndex = 0; TexCoordIndex < MAX_MESH_TEXTURE_COORDS; ++TexCoordIndex)
{
if (RawMesh.WedgeTexCoords[TexCoordIndex].Num() == NumWedges)
{
GeometryData->AddTexCoords(TexCoordIndex);
SimplygonSDK::spRealArray TexCoords = GeometryData->GetTexCoords(TexCoordIndex);
check(TexCoords->GetTupleSize() == 2);
for (int32 WedgeIndex = 0; WedgeIndex < NumWedges; ++WedgeIndex)
{
TexCoords->SetTuple(WedgeIndex, (float*)&RawMesh.WedgeTexCoords[TexCoordIndex][WedgeIndex]);
}
}
}
if (RawMesh.WedgeColors.Num() == NumWedges)
{
GeometryData->AddColors(0);
SimplygonSDK::spRealArray LinearColors = GeometryData->GetColors(0);
check(LinearColors);
check(LinearColors->GetTupleSize() == 4);
for (int32 WedgeIndex = 0; WedgeIndex < NumWedges; ++WedgeIndex)
{
FLinearColor LinearColor(RawMesh.WedgeColors[WedgeIndex]);
LinearColors->SetTuple(WedgeIndex, (float*)&LinearColor);
}
}
if (RawMesh.WedgeTangentZ.Num() == NumWedges)
{
if (RawMesh.WedgeTangentX.Num() == NumWedges && RawMesh.WedgeTangentY.Num() == NumWedges)
{
GeometryData->AddTangents(0);
SimplygonSDK::spRealArray Tangents = GeometryData->GetTangents(0);
for (int32 WedgeIndex = 0; WedgeIndex < NumWedges; ++WedgeIndex)
{
FVector TempTangent = RawMesh.WedgeTangentX[WedgeIndex];
TempTangent.Z = -TempTangent.Z;
Tangents->SetTuple(WedgeIndex, (float*)&TempTangent);
}
GeometryData->AddBitangents(0);
SimplygonSDK::spRealArray Bitangents = GeometryData->GetBitangents(0);
for (int32 WedgeIndex = 0; WedgeIndex < NumWedges; ++WedgeIndex)
{
FVector TempBitangent = RawMesh.WedgeTangentY[WedgeIndex];
TempBitangent.Z = -TempBitangent.Z;
Bitangents->SetTuple(WedgeIndex, (float*)&TempBitangent);
}
}
GeometryData->AddNormals();
SimplygonSDK::spRealArray Normals = GeometryData->GetNormals();
for (int32 WedgeIndex = 0; WedgeIndex < NumWedges; ++WedgeIndex)
{
FVector TempNormal = RawMesh.WedgeTangentZ[WedgeIndex];
TempNormal.Z = -TempNormal.Z;
Normals->SetTuple(WedgeIndex, (float*)&TempNormal);
}
}
// Per-triangle data.
GeometryData->AddMaterialIds();
SimplygonSDK::spRidArray MaterialIndices = GeometryData->GetMaterialIds();
for (int32 TriIndex = 0; TriIndex < NumTris; ++TriIndex)
{
MaterialIndices->SetItem(TriIndex, RawMesh.FaceMaterialIndices[TriIndex]);
}
GeometryData->AddGroupIds();
SimplygonSDK::spRidArray GroupIds = GeometryData->GetGroupIds();
for (int32 TriIndex = 0; TriIndex < NumTris; ++TriIndex)
{
GroupIds->SetItem(TriIndex, RawMesh.FaceSmoothingMasks[TriIndex]);
}
return GeometryData;
}
void CreateRawMeshFromGeometry(FRawMesh& OutRawMesh, const SimplygonSDK::spGeometryData& GeometryData, EWindingMode WindingMode)
{
check(GeometryData);
SimplygonSDK::spRealArray Positions = GeometryData->GetCoords();
SimplygonSDK::spRidArray Indices = GeometryData->GetVertexIds();
SimplygonSDK::spRidArray MaterialIndices = GeometryData->GetMaterialIds();
SimplygonSDK::spRidArray GroupIds = GeometryData->GetGroupIds();
SimplygonSDK::spRealArray LinearColors = GeometryData->GetColors(0);
SimplygonSDK::spRealArray Normals = GeometryData->GetNormals();
SimplygonSDK::spRealArray Tangents = GeometryData->GetTangents(0);
SimplygonSDK::spRealArray Bitangents = GeometryData->GetBitangents(0);
check(Positions);
check(Indices);
FRawMesh& RawMesh = OutRawMesh;
const bool bReverseWinding = (WindingMode == WINDING_Reverse);
int32 NumTris = GeometryData->GetTriangleCount();
int32 NumWedges = NumTris * 3;
int32 NumVertices = GeometryData->GetVertexCount();
RawMesh.VertexPositions.Empty(NumVertices);
RawMesh.VertexPositions.AddUninitialized(NumVertices);
for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex)
{
Positions->GetTuple(VertexIndex, (float*)&RawMesh.VertexPositions[VertexIndex]);
RawMesh.VertexPositions[VertexIndex].Z = -RawMesh.VertexPositions[VertexIndex].Z;
}
RawMesh.WedgeIndices.Empty(NumWedges);
RawMesh.WedgeIndices.AddUninitialized(NumWedges);
for (int32 TriIndex = 0; TriIndex < NumTris; ++TriIndex)
{
for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
{
const uint32 DestIndex = bReverseWinding ? (2 - CornerIndex) : CornerIndex;
RawMesh.WedgeIndices[TriIndex * 3 + DestIndex] = Indices->GetItem(TriIndex * 3 + CornerIndex);
}
}
for(int32 TexCoordIndex = 0; TexCoordIndex < MAX_MESH_TEXTURE_COORDS; ++TexCoordIndex)
{
SimplygonSDK::spRealArray TexCoords = GeometryData->GetTexCoords(TexCoordIndex);
if (TexCoords)
{
RawMesh.WedgeTexCoords[TexCoordIndex].Empty(NumWedges);
RawMesh.WedgeTexCoords[TexCoordIndex].AddUninitialized(NumWedges);
for (int32 TriIndex = 0; TriIndex < NumTris; ++TriIndex)
{
for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
{
const uint32 DestIndex = bReverseWinding ? (2 - CornerIndex) : CornerIndex;
TexCoords->GetTuple(TriIndex * 3 + CornerIndex, (float*)&RawMesh.WedgeTexCoords[TexCoordIndex][TriIndex * 3 + DestIndex]);
}
}
}
}
if (LinearColors)
{
RawMesh.WedgeColors.Empty(NumWedges);
RawMesh.WedgeColors.AddUninitialized(NumWedges);
for (int32 WedgeIndex = 0; WedgeIndex < NumWedges; ++WedgeIndex)
{
FLinearColor LinearColor;
LinearColors->GetTuple(WedgeIndex, (float*)&LinearColor);
RawMesh.WedgeColors[WedgeIndex] = LinearColor.ToFColor(true);
}
}
if (Normals)
{
if (Tangents && Bitangents)
{
RawMesh.WedgeTangentX.Empty(NumWedges);
RawMesh.WedgeTangentX.AddUninitialized(NumWedges);
for (int32 WedgeIndex = 0; WedgeIndex < NumWedges; ++WedgeIndex)
{
Tangents->GetTuple(WedgeIndex, (float*)&RawMesh.WedgeTangentX[WedgeIndex]);
RawMesh.WedgeTangentX[WedgeIndex].Z = -RawMesh.WedgeTangentX[WedgeIndex].Z;
}
RawMesh.WedgeTangentY.Empty(NumWedges);
RawMesh.WedgeTangentY.AddUninitialized(NumWedges);
for (int32 WedgeIndex = 0; WedgeIndex < NumWedges; ++WedgeIndex)
{
Bitangents->GetTuple(WedgeIndex, (float*)&RawMesh.WedgeTangentY[WedgeIndex]);
RawMesh.WedgeTangentY[WedgeIndex].Z = -RawMesh.WedgeTangentY[WedgeIndex].Z;
}
}
RawMesh.WedgeTangentZ.Empty(NumWedges);
RawMesh.WedgeTangentZ.AddUninitialized(NumWedges);
for (int32 WedgeIndex = 0; WedgeIndex < NumWedges; ++WedgeIndex)
{
Normals->GetTuple(WedgeIndex, (float*)&RawMesh.WedgeTangentZ[WedgeIndex]);
RawMesh.WedgeTangentZ[WedgeIndex].Z = -RawMesh.WedgeTangentZ[WedgeIndex].Z;
}
}
RawMesh.FaceMaterialIndices.Empty(NumTris);
RawMesh.FaceMaterialIndices.AddZeroed(NumTris);
if (MaterialIndices)
{
for (int32 TriIndex = 0; TriIndex < NumTris; ++TriIndex)
{
RawMesh.FaceMaterialIndices[TriIndex] = MaterialIndices->GetItem(TriIndex);
}
}
RawMesh.FaceSmoothingMasks.Empty(NumTris);
RawMesh.FaceSmoothingMasks.AddZeroed(NumTris);
if (GroupIds)
{
for (int32 TriIndex = 0; TriIndex < NumTris; ++TriIndex)
{
RawMesh.FaceSmoothingMasks[TriIndex] = GroupIds->GetItem(TriIndex);
}
}
}
void SetReductionSettings(SimplygonSDK::spReductionSettings ReductionSettings, const FMeshReductionSettings& Settings, int32 NumTris)
{
float MaxDeviation = Settings.MaxDeviation > 0.0f ? Settings.MaxDeviation : FLT_MAX;
float MinReductionRatio = FMath::Max<float>(1.0f / NumTris, 0.05f);
float MaxReductionRatio = (Settings.MaxDeviation > 0.0f && Settings.PercentTriangles == 1.0f) ? MinReductionRatio : 1.0f;
float ReductionRatio = FMath::Clamp(Settings.PercentTriangles, MinReductionRatio, MaxReductionRatio);
unsigned int FeatureFlagsMask = SimplygonSDK::SG_FEATUREFLAGS_GROUP;
if (Settings.TextureImportance != EMeshFeatureImportance::Off)
{
FeatureFlagsMask |= (SimplygonSDK::SG_FEATUREFLAGS_TEXTURE0 | SimplygonSDK::SG_FEATUREFLAGS_MATERIAL);
}
if (Settings.ShadingImportance != EMeshFeatureImportance::Off)
{
FeatureFlagsMask |= SimplygonSDK::SG_FEATUREFLAGS_SHADING;
}
const float ImportanceTable[] =
{
0.0f, // OFF
0.125f, // Lowest
0.35f, // Low,
1.0f, // Normal
2.8f, // High
8.0f, // Highest
};
static_assert(ARRAY_COUNT(ImportanceTable) == (EMeshFeatureImportance::Highest + 1), "Importance table size mismatch."); // -1 because of TEMP_BROKEN(?)
check(Settings.SilhouetteImportance < EMeshFeatureImportance::Highest+1); // -1 because of TEMP_BROKEN(?)
check(Settings.TextureImportance < EMeshFeatureImportance::Highest+1); // -1 because of TEMP_BROKEN(?)
check(Settings.ShadingImportance < EMeshFeatureImportance::Highest+1); // -1 because of TEMP_BROKEN(?)
ReductionSettings->SetFeatureFlags(FeatureFlagsMask);
ReductionSettings->SetMaxDeviation(MaxDeviation);
ReductionSettings->SetReductionRatio(ReductionRatio);
ReductionSettings->SetGeometryImportance(ImportanceTable[Settings.SilhouetteImportance]);
ReductionSettings->SetTextureImportance(ImportanceTable[Settings.TextureImportance]);
ReductionSettings->SetMaterialImportance(ImportanceTable[Settings.TextureImportance]);
ReductionSettings->SetShadingImportance( ImportanceTable[Settings.ShadingImportance]);
ReductionSettings->SetAllowDirectX(false);
}
void SetNormalSettings(SimplygonSDK::spNormalCalculationSettings NormalSettings, const FMeshReductionSettings& Settings)
{
NormalSettings->SetReplaceNormals(Settings.bRecalculateNormals);
NormalSettings->SetScaleByArea(false);
NormalSettings->SetScaleByAngle(false);
NormalSettings->SetHardEdgeAngle(Settings.HardAngleThreshold);
}
/**
* Creates a Simplygon scene representation of the skeletal hierarchy.
* @param InScene - The Simplygon scene.
* @param InSkeleton - The skeletal hierarchy from which to create the Simplygon representation.
* @param OutBoneTableIDs - A mapping of Bone IDs from RefSkeleton to Simplygon Bone Table IDs
*/
void CreateSkeletalHierarchy( SimplygonSDK::spScene& InScene, const FReferenceSkeleton& InSkeleton, TArray<SimplygonSDK::rid>& OutBoneTableIDs )
{
TArray<SimplygonSDK::spSceneBone> BoneArray;
//Create Simplygon scene nodes for each bone in our Skeletal Hierarchy
for(int32 BoneIndex=0; BoneIndex < InSkeleton.GetNum(); ++BoneIndex)
{
SimplygonSDK::spSceneBone SceneBone = SDK->CreateSceneBone();
BoneArray.Add(SceneBone);
}
SimplygonSDK::spSceneBoneTable BoneTable = InScene->GetBoneTable();
for(int32 BoneIndex=0; BoneIndex < InSkeleton.GetNum(); ++BoneIndex)
{
int32 ParentIndex = InSkeleton.GetParentIndex(BoneIndex);
SimplygonSDK::spSceneBone CurrentBone = BoneArray[BoneIndex];
//We add the bone to the scene's bone table. This returns a uid that we must apply to the geometry data.
SimplygonSDK::rid BoneID = BoneTable->AddBone(CurrentBone);
OutBoneTableIDs.Add(BoneID);
//Handle root bone. Add to Scene root.
if(BoneIndex == 0)
{
InScene->GetRootNode()->AddChild(CurrentBone);
}
else
{
SimplygonSDK::spSceneBone ParentBone = BoneArray[ParentIndex];
ParentBone->AddChild(CurrentBone);
}
}
}
/**
* Creates a Simplygon geometry representation from a skeletal mesh LOD model.
* @param Scene - The Simplygon scene.
* @param LODModel - The skeletal mesh LOD model from which to create the geometry data.
* @param BoneIDs - A maps of Bone IDs from RefSkeleton to Simplygon BoneTable IDs
* @returns a Simplygon geometry data representation of the skeletal mesh LOD.
*/
SimplygonSDK::spGeometryData CreateGeometryFromSkeletalLODModel( SimplygonSDK::spScene& Scene, const FStaticLODModel& LODModel, const TArray<SimplygonSDK::rid>& BoneIDs )
{
TArray<FSoftSkinVertex> Vertices;
FMultiSizeIndexContainerData IndexData;
LODModel.GetVertices( Vertices );
LODModel.MultiSizeIndexContainer.GetIndexBufferData( IndexData );
const uint32 VertexCount = LODModel.NumVertices;
const uint32 TexCoordCount = LODModel.NumTexCoords;
check( TexCoordCount <= MAX_TEXCOORDS );
uint32 TriCount = 0;
#if WITH_APEX_CLOTHING
const uint32 SectionCount = (uint32)LODModel.NumNonClothingSections();
#else
const uint32 SectionCount = LODModel.Sections.Num();
#endif // #if WITH_APEX_CLOTHING
for ( uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex )
{
TriCount += LODModel.Sections[ SectionIndex ].NumTriangles;
}
SimplygonSDK::spGeometryData GeometryData = SDK->CreateGeometryData();
GeometryData->SetVertexCount( VertexCount );
GeometryData->SetTriangleCount( TriCount );
// Per-vertex data.
GeometryData->AddBoneIds( MAX_TOTAL_INFLUENCES );
GeometryData->AddBoneWeights( MAX_TOTAL_INFLUENCES );
SimplygonSDK::spRealArray Positions = GeometryData->GetCoords();
SimplygonSDK::spRidArray BoneIds = GeometryData->GetBoneIds();
SimplygonSDK::spRealArray BoneWeights = GeometryData->GetBoneWeights();
// Per-corner per-triangle data.
SimplygonSDK::spRidArray Indices = GeometryData->GetVertexIds();
SimplygonSDK::spRealArray TexCoords[MAX_TEXCOORDS];
for( uint32 TexCoordIndex = 0; TexCoordIndex < TexCoordCount; ++TexCoordIndex )
{
GeometryData->AddTexCoords( TexCoordIndex );
TexCoords[TexCoordIndex] = GeometryData->GetTexCoords( TexCoordIndex );
check( TexCoords[TexCoordIndex]->GetTupleSize() == 2 );
}
GeometryData->AddNormals();
GeometryData->AddTangents(0);
GeometryData->AddBitangents(0);
SimplygonSDK::spRealArray Normals = GeometryData->GetNormals();
SimplygonSDK::spRealArray Tangents = GeometryData->GetTangents(0);
SimplygonSDK::spRealArray Bitangents = GeometryData->GetBitangents(0);
SimplygonSDK::spUnsignedCharArray Colors;
if ( LODModel.ColorVertexBuffer.GetNumVertices() == VertexCount )
{
GeometryData->AddBaseTypeUserTriangleVertexField( SimplygonSDK::TYPES_ID_UCHAR, SIMPLYGON_COLOR_CHANNEL, 4 );
SimplygonSDK::spValueArray ColorValues = GeometryData->GetUserTriangleVertexField( SIMPLYGON_COLOR_CHANNEL );
check( ColorValues );
Colors = SimplygonSDK::IUnsignedCharArray::SafeCast( ColorValues );
check( Colors );
}
// Per-triangle data.
GeometryData->AddMaterialIds();
SimplygonSDK::spRidArray MaterialIndices = GeometryData->GetMaterialIds();
// Add per-vertex data. This data needs to be added per-chunk so that we can properly map bones.
#if WITH_APEX_CLOTHING
const int32 ChunkCount = LODModel.NumNonClothingSections();
#else
const int32 ChunkCount = LODModel.Chunks.Num();
#endif // #if WITH_APEX_CLOTHING
uint32 FirstVertex = 0;
for ( int32 ChunkIndex = 0; ChunkIndex < ChunkCount; ++ChunkIndex )
{
const FSkelMeshChunk& Chunk = LODModel.Chunks[ ChunkIndex ];
const uint32 LastVertex = FirstVertex + (uint32)Chunk.RigidVertices.Num() + (uint32)Chunk.SoftVertices.Num();
for ( uint32 VertexIndex = FirstVertex; VertexIndex < LastVertex; ++VertexIndex )
{
FSoftSkinVertex& Vertex = Vertices[ VertexIndex ];
SimplygonSDK::rid VertexBoneIds[MAX_TOTAL_INFLUENCES];
SimplygonSDK::real VertexBoneWeights[MAX_TOTAL_INFLUENCES];
uint32 TotalInfluence = 0;
for ( uint32 InfluenceIndex = 0; InfluenceIndex < MAX_TOTAL_INFLUENCES; ++InfluenceIndex )
{
int32 BoneIndex = Vertex.InfluenceBones[InfluenceIndex];
const uint8 BoneInfluence = Vertex.InfluenceWeights[InfluenceIndex];
TotalInfluence += BoneInfluence;
if ( BoneInfluence > 0 )
{
check( BoneIndex < Chunk.BoneMap.Num() );
uint32 BoneID = BoneIDs[ Chunk.BoneMap[ BoneIndex ] ];
VertexBoneIds[InfluenceIndex] = BoneID;
VertexBoneWeights[InfluenceIndex] = BoneInfluence / 255.0f;
}
else
{
//Set BoneID and BoneWeight to -1 and 0 respectively as required by simplygon.
VertexBoneIds[InfluenceIndex] = -1;
VertexBoneWeights[InfluenceIndex] = 0;
}
}
check( TotalInfluence == 255 );
Positions->SetTuple( VertexIndex, (float*)&Vertex.Position );
BoneIds->SetTuple( VertexIndex, VertexBoneIds );
BoneWeights->SetTuple( VertexIndex, VertexBoneWeights );
}
FirstVertex = LastVertex;
}
// Add per-vertex per-triangle data.
for ( uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex )
{
const FSkelMeshSection& Section = LODModel.Sections[ SectionIndex ];
const uint32 FirstIndex = Section.BaseIndex;
const uint32 LastIndex = FirstIndex + Section.NumTriangles * 3;
for ( uint32 Index = FirstIndex; Index < LastIndex; ++Index )
{
uint32 VertexIndex = IndexData.Indices[ Index ];
FSoftSkinVertex& Vertex = Vertices[ VertexIndex ];
FVector Normal = Vertex.TangentZ;
FVector Tangent = Vertex.TangentX;
FVector Bitangent = Vertex.TangentY;
Indices->SetItem( Index, VertexIndex );
Normals->SetTuple( Index, (float*)&Normal );
Tangents->SetTuple( Index, (float*)&Tangent );
Bitangents->SetTuple( Index, (float*)&Bitangent );
for( uint32 TexCoordIndex = 0; TexCoordIndex < TexCoordCount; ++TexCoordIndex )
{
FVector2D TexCoord = Vertex.UVs[TexCoordIndex];
TexCoords[TexCoordIndex]->SetTuple( Index, (float*)&TexCoord );
}
if ( Colors )
{
FColor Color = LODModel.ColorVertexBuffer.VertexColor( VertexIndex );
Colors->SetTuple( Index, (UCHAR*)&Color );
}
}
}
// Add per-triangle data.
for ( uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex )
{
const FSkelMeshSection& Section = LODModel.Sections[ SectionIndex ];
const uint32 FirstTriIndex = Section.BaseIndex / 3;
const uint32 LastTriIndex = FirstTriIndex + Section.NumTriangles - 1;
const uint16 MaterialIndex = Section.MaterialIndex;
for ( uint32 TriIndex = FirstTriIndex; TriIndex <= LastTriIndex; ++TriIndex )
{
MaterialIndices->SetItem( TriIndex, MaterialIndex );
check( MaterialIndices->GetItem( TriIndex) == MaterialIndex );
}
}
// Create a new simplygon scene mesh object
SimplygonSDK::spSceneMesh Mesh = SDK->CreateSceneMesh();
// Assign the geometry data to the mesh
Mesh->SetGeometry( GeometryData );
// Add Mesh to the scene
Scene->GetRootNode()->AddChild( Mesh );
return GeometryData;
}
/**
* Holds data needed to create skeletal mesh skinning streams.
*/
struct FSkeletalMeshData
{
TArray<FVertInfluence> Influences;
TArray<FMeshWedge> Wedges;
TArray<FMeshFace> Faces;
TArray<FVector> Points;
uint32 TexCoordCount;
};
/**
* Extracts mesh data from the Simplygon geometry representation in to a set of data usable by skeletal meshes.
* @param GeometryData - the Simplygon geometry.
* @param MeshData - the skeletal mesh output data.
*/
void ExtractSkeletalDataFromGeometry( const SimplygonSDK::spGeometryData& GeometryData, FSkeletalMeshData& MeshData )
{
TArray<FVector> PointNormals;
TArray<uint32> PointList;
TArray<uint32> PointInfluenceMap;
check( GeometryData );
SimplygonSDK::spRealArray Positions = GeometryData->GetCoords();
SimplygonSDK::spRidArray Indices = GeometryData->GetVertexIds();
SimplygonSDK::spRidArray MaterialIndices = GeometryData->GetMaterialIds();
SimplygonSDK::spValueArray VertexColorValues = GeometryData->GetUserTriangleVertexField( SIMPLYGON_COLOR_CHANNEL );
SimplygonSDK::spUnsignedCharArray VertexColors = SimplygonSDK::IUnsignedCharArray::SafeCast( VertexColorValues );
SimplygonSDK::spRealArray Normals = GeometryData->GetNormals();
SimplygonSDK::spRealArray Tangents = GeometryData->GetTangents(0);
SimplygonSDK::spRealArray Bitangents = GeometryData->GetBitangents(0);
SimplygonSDK::spRidArray BoneIds = GeometryData->GetBoneIds();
SimplygonSDK::spRealArray BoneWeights = GeometryData->GetBoneWeights();
SimplygonSDK::spRealArray TexCoords[MAX_TEXCOORDS];
uint32 TexCoordCount = 0;
for( uint32 TexCoordIndex = 0; TexCoordIndex < MAX_TEXCOORDS; ++TexCoordIndex )
{
TexCoords[TexCoordIndex] = GeometryData->GetTexCoords(TexCoordIndex);
if ( TexCoords[TexCoordIndex] == NULL )
{
break;
}
TexCoordCount++;
}
MeshData.TexCoordCount = TexCoordCount;
check( Positions );
check( Indices );
check( MaterialIndices );
check( Normals );
check( Tangents );
check( Bitangents );
check( BoneIds );
check( BoneWeights );
const bool bHaveColors = (VertexColors != NULL);
const uint32 VertexCount = GeometryData->GetVertexCount();
const uint32 TriCount = GeometryData->GetTriangleCount();
// Initialize the lists of points, wedges, and faces.
MeshData.Points.AddZeroed( VertexCount );
PointNormals.AddZeroed( VertexCount );
PointList.Reserve( VertexCount );
PointInfluenceMap.Reserve( VertexCount );
for ( uint32 PointIndex = 0; PointIndex < VertexCount; ++PointIndex )
{
PointList.Add( INDEX_NONE );
PointInfluenceMap.Add( INDEX_NONE );
}
MeshData.Wedges.AddZeroed( TriCount * 3 );
MeshData.Faces.AddZeroed( TriCount );
//The number of influences may have changed if we have specified a max number of bones per vertex.
uint32 NumOfInfluences = FMath::Min<uint32>( BoneIds->GetTupleSize() , MAX_TOTAL_INFLUENCES );
// Per-vertex data.
for ( uint32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex )
{
FVector& Point = MeshData.Points[ VertexIndex ];
SimplygonSDK::rid VertexBoneIds[MAX_TOTAL_INFLUENCES];
SimplygonSDK::real VertexBoneWeights[MAX_TOTAL_INFLUENCES];
Positions->GetTuple( VertexIndex, (float*)&Point );
BoneIds->GetTuple( VertexIndex, VertexBoneIds );
BoneWeights->GetTuple( VertexIndex, VertexBoneWeights );
PointInfluenceMap[ VertexIndex ] = (uint32)MeshData.Influences.Num();
for ( uint32 InfluenceIndex = 0; InfluenceIndex < NumOfInfluences; ++InfluenceIndex )
{
const uint16 BoneIndex = VertexBoneIds[InfluenceIndex];
const float BoneWeight = VertexBoneWeights[InfluenceIndex];
if ( InfluenceIndex == 0 || BoneWeight > 0.0f )
{
FVertInfluence* VertInfluence = new(MeshData.Influences) FVertInfluence;
VertInfluence->BoneIndex = BoneIndex;
VertInfluence->Weight = BoneWeight;
VertInfluence->VertIndex = VertexIndex;
}
}
}
// Per-triangle and per-corner data.
for ( uint32 TriIndex = 0; TriIndex < TriCount; ++TriIndex )
{
// Per-triangle data.
FMeshFace& Face = MeshData.Faces[ TriIndex ];
Face.MeshMaterialIndex = MaterialIndices->GetItem( TriIndex );
// Per-corner data.
for( uint32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex )
{
const uint32 WedgeIndex = TriIndex * 3 + CornerIndex;
const uint32 BasePointIndex = (uint32)Indices->GetItem( WedgeIndex );
uint32 PointIndex = BasePointIndex;
check( BasePointIndex < (uint32)PointList.Num() );
// Duplicate points where needed to create hard edges.
FVector WedgeNormal;
Normals->GetTuple( WedgeIndex, (float*)&WedgeNormal );
WedgeNormal.Normalize();
FVector PointNormal = PointNormals[ PointIndex ];
if ( PointNormal.SizeSquared() < KINDA_SMALL_NUMBER )
{
PointNormals[ PointIndex ] = WedgeNormal;
}
else
{
while ( (PointNormal | WedgeNormal) - 1.0f < -KINDA_SMALL_NUMBER )
{
PointIndex = PointList[ PointIndex ];
if ( PointIndex == INDEX_NONE )
{
break;
}
check( PointIndex < (uint32)PointList.Num() );
PointNormal = PointNormals[ PointIndex ];
}
if ( PointIndex == INDEX_NONE )
{
FVector Point = MeshData.Points[ BasePointIndex ];
PointIndex = MeshData.Points.Add( Point );
check( PointIndex == (uint32)PointList.Num() );
check( PointIndex == (uint32)PointInfluenceMap.Num() );
check( PointIndex == (uint32)PointNormals.Num() );
PointNormals.Add( WedgeNormal );
uint32 NextPointIndex = PointList[ BasePointIndex ];
check( NextPointIndex < (uint32)PointList.Num() || NextPointIndex == INDEX_NONE );
PointList[ BasePointIndex ] = PointIndex;
PointList.Add( NextPointIndex );
PointInfluenceMap.Add( (uint32)MeshData.Influences.Num() );
int32 InfluenceIndex = PointInfluenceMap[ BasePointIndex ];
while ( MeshData.Influences[ InfluenceIndex ].VertIndex == BasePointIndex )
{
FVertInfluence* NewVertInfluence = new( MeshData.Influences ) FVertInfluence;
NewVertInfluence->BoneIndex = MeshData.Influences[ InfluenceIndex ].BoneIndex;
NewVertInfluence->Weight = MeshData.Influences[ InfluenceIndex ].Weight;
NewVertInfluence->VertIndex = PointIndex;
InfluenceIndex++;
}
check( PointNormals.Num() == MeshData.Points.Num() );
check( PointList.Num() == MeshData.Points.Num() );
check( PointInfluenceMap.Num() == MeshData.Points.Num() );
}
}
check( PointIndex != INDEX_NONE );
check( ( MeshData.Points[ PointIndex ] - MeshData.Points[ BasePointIndex ] ).SizeSquared() == 0.0f );
FMeshWedge& Wedge = MeshData.Wedges[ WedgeIndex ];
Wedge.iVertex = PointIndex;
for( uint32 TexCoordIndex = 0; TexCoordIndex < TexCoordCount; ++TexCoordIndex )
{
TexCoords[TexCoordIndex]->GetTuple( WedgeIndex, (float*)&Wedge.UVs[TexCoordIndex] );
}
if( bHaveColors )
{
VertexColors->GetTuple( WedgeIndex, (uint8 *)&Wedge.Color );
}
else
{
Wedge.Color = FColor( 255, 255, 255, 255 );
}
Face.iWedge[CornerIndex] = WedgeIndex;
}
}
}
/**
* Creates a skeletal mesh LOD model from the Simplygon geometry representation.
* @param GeometryData - the Simplygon geometry representation from which to create a skeletal mesh LOD.
* @param SkeletalMesh - the skeletal mesh in to which the LOD model will be added.
* @param NewModel - the LOD model in to which the geometry will be stored.
*/
void CreateSkeletalLODModelFromGeometry( const SimplygonSDK::spGeometryData& GeometryData, const FReferenceSkeleton& RefSkeleton, FStaticLODModel* NewModel )
{
check( GeometryData );
FSkeletalMeshData MeshData;
ExtractSkeletalDataFromGeometry( GeometryData, MeshData );
// Create dummy map of 'point to original'
TArray<int32> DummyMap;
DummyMap.AddUninitialized(MeshData.Points.Num());
for(int32 PointIdx = 0; PointIdx<MeshData.Points.Num(); PointIdx++)
{
DummyMap[PointIdx] = PointIdx;
}
IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>("MeshUtilities");
// Create skinning streams for NewModel.
MeshUtilities.BuildSkeletalMesh(
*NewModel,
RefSkeleton,
MeshData.Influences,
MeshData.Wedges,
MeshData.Faces,
MeshData.Points,
DummyMap
);
// Set texture coordinate count on the new model.
NewModel->NumTexCoords = MeshData.TexCoordCount;
NewModel->Size = 0;
}
/**
* Sets reduction settings for Simplygon.
* @param Settings - The skeletal mesh optimization settings.
* @param ReductionSettings - The reduction settings to set for Simplygon.
*/
void SetReductionSettings( const FSkeletalMeshOptimizationSettings& Settings, float BoundsRadius, int32 SourceTriCount, SimplygonSDK::spReductionSettings ReductionSettings )
{
// Compute max deviation from quality.
float MaxDeviation = Settings.MaxDeviationPercentage > 0.0f ? Settings.MaxDeviationPercentage * BoundsRadius : SimplygonSDK::REAL_MAX;
// Set the reduction ratio such that at least 1 triangle or 5% of the original triangles remain, whichever is larger.
float MinReductionRatio = FMath::Max<float>(1.0f / SourceTriCount, 0.05f);
float MaxReductionRatio = (Settings.MaxDeviationPercentage > 0.0f && Settings.NumOfTrianglesPercentage == 1.0f) ? MinReductionRatio : 1.0f;
float ReductionRatio = FMath::Clamp(Settings.NumOfTrianglesPercentage, MinReductionRatio, MaxReductionRatio);
//Enable feature flags for those features where we have set an importance
unsigned int FeatureFlagsMask = SimplygonSDK::SG_FEATUREFLAGS_GROUP;
if( Settings.TextureImportance != SMOI_Off )
{
FeatureFlagsMask |= (SimplygonSDK::SG_FEATUREFLAGS_TEXTURE0 | SimplygonSDK::SG_FEATUREFLAGS_MATERIAL);
}
if( Settings.ShadingImportance != SMOI_Off )
{
FeatureFlagsMask |= SimplygonSDK::SG_FEATUREFLAGS_SHADING;
}
const float ImportanceTable[] =
{
0.0f, // OFF
0.125f, // Lowest
0.35f, // Low,
1.0f, // Normal
2.8f, // High
8.0f, // Highest
};
static_assert(ARRAY_COUNT(ImportanceTable) == SMOI_MAX, "Bad importance table size.");
check( Settings.SilhouetteImportance < SMOI_MAX );
check( Settings.TextureImportance < SMOI_MAX );
check( Settings.ShadingImportance < SMOI_MAX );
check( Settings.SkinningImportance < SMOI_MAX );
ReductionSettings->SetFeatureFlags( FeatureFlagsMask );
ReductionSettings->SetMaxDeviation( MaxDeviation );
ReductionSettings->SetReductionRatio( ReductionRatio );
ReductionSettings->SetGeometryImportance( ImportanceTable[Settings.SilhouetteImportance] );
ReductionSettings->SetTextureImportance( ImportanceTable[Settings.TextureImportance] );
ReductionSettings->SetMaterialImportance( ImportanceTable[Settings.TextureImportance] );
ReductionSettings->SetShadingImportance( ImportanceTable[Settings.ShadingImportance] );
ReductionSettings->SetSkinningImportance( ImportanceTable[Settings.SkinningImportance] );
}
/**
* Sets vertex normal settings for Simplygon.
* @param Settings - The skeletal mesh optimization settings.
* @param NormalSettings - The normal settings to set for Simplygon.
*/
void SetNormalSettings( const FSkeletalMeshOptimizationSettings& Settings, SimplygonSDK::spNormalCalculationSettings NormalSettings )
{
NormalSettings->SetReplaceNormals( Settings.bRecalcNormals );
NormalSettings->SetScaleByArea( false );
NormalSettings->SetScaleByAngle( false );
NormalSettings->SetHardEdgeAngle( Settings.NormalsThreshold );
}
/**
* Sets Bone Lod settings for Simplygon.
* @param Settings - The skeletal mesh optimization settings.
* @param NormalSettings - The Bone LOD to set for Simplygon.
*/
void SetBoneSettings( const FSkeletalMeshOptimizationSettings& Settings, SimplygonSDK::spBoneSettings BoneSettings)
{
BoneSettings->SetBoneLodProcess( SimplygonSDK::SG_BONEPROCESSING_RATIO_PROCESSING );
BoneSettings->SetBoneLodRatio ( Settings.BoneReductionRatio );
BoneSettings->SetMaxBonePerVertex( Settings.MaxBonesPerVertex );
}
/**
* Calculates the view distance that a mesh should be displayed at.
* @param MaxDeviation - The maximum surface-deviation between the reduced geometry and the original. This value should be acquired from Simplygon
* @returns The calculated view distance
*/
float CalculateViewDistance( float MaxDeviation )
{
// We want to solve for the depth in world space given the screen space distance between two pixels
//
// Assumptions:
// 1. There is no scaling in the view matrix.
// 2. The horizontal FOV is 90 degrees.
// 3. The backbuffer is 1920x1080.
//
// If we project two points at (X,Y,Z) and (X',Y,Z) from view space, we get their screen
// space positions: (X/Z, Y'/Z) and (X'/Z, Y'/Z) where Y' = Y * AspectRatio.
//
// The distance in screen space is then sqrt( (X'-X)^2/Z^2 + (Y'-Y')^2/Z^2 )
// or (X'-X)/Z. This is in clip space, so PixelDist = 1280 * 0.5 * (X'-X)/Z.
//
// Solving for Z: ViewDist = (X'-X * 640) / PixelDist
const float ViewDistance = (MaxDeviation * 960.0f);
return ViewDistance;
}
/**
* ProxyLOD Related Methods
*/
void SetMaterialChannelData(
const TArray<FColor>& InSamples,
FIntPoint InTextureSize,
SimplygonSDK::spMaterial& InSGMaterial,
const char* SGMaterialChannelName)
{
int32 NumTexels = InTextureSize.X * InTextureSize.Y;
check(NumTexels == InSamples.Num())
if (NumTexels > 1)
{
SimplygonSDK::spImageData ImageData = SDK->CreateImageData();
ImageData->AddColors(SimplygonSDK::TYPES_ID_UCHAR, SimplygonSDK::SG_IMAGEDATA_FORMAT_RGBA);
ImageData->Set2DSize(InTextureSize.X, InTextureSize.Y);
SimplygonSDK::spUnsignedCharArray ImageColors = SimplygonSDK::SafeCast<SimplygonSDK::IUnsignedCharArray>(ImageData->GetColors());
//Set the texture data to simplygon color data texel per texel
ImageColors->SetTupleCount(NumTexels);
for (int32 TexelIndex = 0; TexelIndex < NumTexels; TexelIndex++)
{
// BGRA -> RGBA
uint8 Texel[4];
Texel[0] = InSamples[TexelIndex].R;
Texel[1] = InSamples[TexelIndex].G;
Texel[2] = InSamples[TexelIndex].B;
Texel[3] = (SGMaterialChannelName == SimplygonSDK::SG_MATERIAL_CHANNEL_DIFFUSE ? InSamples[TexelIndex].A : FColor::White.A);
ImageColors->SetTuple(TexelIndex, Texel);
}
InSGMaterial->SetLayeredTextureImage(SGMaterialChannelName, 0, ImageData);
InSGMaterial->SetLayeredTextureLevel(SGMaterialChannelName, 0, 0);
}
else if (NumTexels == 1)
{
// handle uniform value
InSGMaterial->SetColorRGB(SGMaterialChannelName, InSamples[0].R, InSamples[0].G, InSamples[0].B);
}
}
//Material conversions
bool CreateSGMaterialFromFlattenMaterial(
const TArray<MaterialExportUtils::FFlattenMaterial>& InputMaterials,
SimplygonSDK::spMaterialTable& OutSGMaterialTable,
FMaterialCastingProperties& OutCastProperties)
{
if (InputMaterials.Num() == 0)
{
//If there are no materials, feed Simplygon with a default material instead.
UE_LOG(LogSimplygon, Log, TEXT("Input meshes do not contain any materials. A proxy without material will be generated."));
return false;
}
for (int32 MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++)
{
SimplygonSDK::spMaterial SGMaterial = SDK->CreateMaterial();
const MaterialExportUtils::FFlattenMaterial& FlattenMaterial = InputMaterials[MaterialIndex];
//Create UE4 Channels
//Simplygon 5.5 has three new channels already present called base color metallic roughness
//To conform with older simplygon versions:
if(!SGMaterial->HasUserChannel(SimplygonSDK::SG_MATERIAL_CHANNEL_BASECOLOR))
SGMaterial->AddUserChannel(SimplygonSDK::SG_MATERIAL_CHANNEL_BASECOLOR);
if(!SGMaterial->HasUserChannel(SimplygonSDK::SG_MATERIAL_CHANNEL_METALLIC))
SGMaterial->AddUserChannel(SimplygonSDK::SG_MATERIAL_CHANNEL_METALLIC);
if(!SGMaterial->HasUserChannel(SimplygonSDK::SG_MATERIAL_CHANNEL_ROUGHNESS))
SGMaterial->AddUserChannel(SimplygonSDK::SG_MATERIAL_CHANNEL_ROUGHNESS);
// We actually use these channels for metallic, roughness and specular
if(!SGMaterial->HasUserChannel(USER_MATERIAL_CHANNEL_METALLIC))
SGMaterial->AddUserChannel(USER_MATERIAL_CHANNEL_METALLIC);
if(!SGMaterial->HasUserChannel(USER_MATERIAL_CHANNEL_ROUGHNESS))
SGMaterial->AddUserChannel(USER_MATERIAL_CHANNEL_ROUGHNESS);
if(!SGMaterial->HasUserChannel(USER_MATERIAL_CHANNEL_SPECULAR))
SGMaterial->AddUserChannel(USER_MATERIAL_CHANNEL_SPECULAR);
SGMaterial->SetName(TCHAR_TO_ANSI(*FString::Printf(TEXT("Material%d"), MaterialIndex)));
// BaseColor
if (FlattenMaterial.DiffuseSamples.Num())
{
SetMaterialChannelData(FlattenMaterial.DiffuseSamples, FlattenMaterial.DiffuseSize, SGMaterial, SimplygonSDK::SG_MATERIAL_CHANNEL_DIFFUSE);
}
// Normal
if (FlattenMaterial.NormalSamples.Num())
{
OutCastProperties.bCastNormals = true;
SetMaterialChannelData(FlattenMaterial.NormalSamples, FlattenMaterial.NormalSize, SGMaterial, SimplygonSDK::SG_MATERIAL_CHANNEL_NORMALS);
}
// Metallic
if (FlattenMaterial.MetallicSamples.Num())
{
OutCastProperties.bCastMetallic = true;
SetMaterialChannelData(FlattenMaterial.MetallicSamples, FlattenMaterial.MetallicSize, SGMaterial, USER_MATERIAL_CHANNEL_METALLIC);
}
// Roughness
if (FlattenMaterial.RoughnessSamples.Num())
{
OutCastProperties.bCastRoughness = true;
SetMaterialChannelData(FlattenMaterial.RoughnessSamples, FlattenMaterial.RoughnessSize, SGMaterial, USER_MATERIAL_CHANNEL_ROUGHNESS);
}
// Specular
if (FlattenMaterial.SpecularSamples.Num())
{
OutCastProperties.bCastSpecular = true;
SetMaterialChannelData(FlattenMaterial.SpecularSamples, FlattenMaterial.SpecularSize, SGMaterial, USER_MATERIAL_CHANNEL_SPECULAR);
}
OutSGMaterialTable->AddMaterial(SGMaterial);
}
return true;
}
void GetMaterialChannelData(const SimplygonSDK::spMaterial& InSGMaterial, const char* SGMaterialChannelName, TArray<FColor>& OutSamples, FIntPoint& OutTextureSize)
{
SimplygonSDK::spImageData SGChannelData = InSGMaterial->GetLayeredTextureImage(SGMaterialChannelName, 0);
if (SGChannelData)
{
SimplygonSDK::spUnsignedCharArray ImageColors = SimplygonSDK::SafeCast<SimplygonSDK::IUnsignedCharArray>(SGChannelData->GetColors());
OutTextureSize.X = SGChannelData->GetXSize();
OutTextureSize.Y = SGChannelData->GetYSize();
int32 TexelsCount = OutTextureSize.X*OutTextureSize.Y;
OutSamples.Empty(TexelsCount);
OutSamples.AddUninitialized(TexelsCount);
for (int32 TexelIndex = 0; TexelIndex < TexelsCount; ++TexelIndex)
{
uint8 ColorData[4];
ImageColors->GetTuple(TexelIndex, (unsigned char*)&ColorData);
OutSamples[TexelIndex].R = ColorData[0];
OutSamples[TexelIndex].G = ColorData[1];
OutSamples[TexelIndex].B = ColorData[2];
OutSamples[TexelIndex].A = (SGMaterialChannelName == SimplygonSDK::SG_MATERIAL_CHANNEL_DIFFUSE ? ColorData[3] : FColor::White.A);
}
}
}
void CreateFlattenMaterialFromSGMaterial(
SimplygonSDK::spMaterialTable& InSGMaterialTable,
MaterialExportUtils::FFlattenMaterial& OutMaterial)
{
SimplygonSDK::spMaterial SGMaterial = InSGMaterialTable->GetMaterial(0);
// Diffuse
GetMaterialChannelData(SGMaterial, SimplygonSDK::SG_MATERIAL_CHANNEL_DIFFUSE, OutMaterial.DiffuseSamples, OutMaterial.DiffuseSize);
// Normal
GetMaterialChannelData(SGMaterial, SimplygonSDK::SG_MATERIAL_CHANNEL_NORMALS, OutMaterial.NormalSamples, OutMaterial.NormalSize);
// Metallic
GetMaterialChannelData(SGMaterial, USER_MATERIAL_CHANNEL_METALLIC, OutMaterial.MetallicSamples, OutMaterial.MetallicSize);
// Roughness
GetMaterialChannelData(SGMaterial, USER_MATERIAL_CHANNEL_ROUGHNESS, OutMaterial.RoughnessSamples, OutMaterial.RoughnessSize);
// Specular
GetMaterialChannelData(SGMaterial, USER_MATERIAL_CHANNEL_SPECULAR, OutMaterial.SpecularSamples, OutMaterial.SpecularSize);
}
};
TScopedPointer<FSimplygonMeshReduction> GSimplygonMeshReduction;
void FSimplygonMeshReductionModule::StartupModule()
{
GSimplygonMeshReduction = FSimplygonMeshReduction::Create();
}
void FSimplygonMeshReductionModule::ShutdownModule()
{
GSimplygonMeshReduction = NULL;
}
IMeshReduction* FSimplygonMeshReductionModule::GetMeshReductionInterface()
{
return GSimplygonMeshReduction;
}
IMeshMerging* FSimplygonMeshReductionModule::GetMeshMergingInterface()
{
return GSimplygonMeshReduction;
}
#undef LOCTEXT_NAMESPACE