// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. #include "UnrealEd.h" #include "RawMesh.h" #include "MeshUtilities.h" #include "SimplygonSDK.h" #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 const FString& GetVersionString() const override { return VersionString; } virtual void Reduce( FRawMesh& OutReducedMesh, float& OutMaxDeviation, const FRawMesh& InMesh, const FMeshReductionSettings& InSettings ) { 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 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 ) { 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 ReregisterContext; SkeletalMesh->ReleaseResources(); SkeletalMesh->ReleaseResourcesFence.Wait(); // Insert a new LOD model entry if needed. if ( LODIndex == SkeletalMeshResource->LODModels.Num() ) { SkeletalMeshResource->LODModels.AddRawItem(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.GetTypedData(); 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 BonesToRemove; IMeshBoneReductionModule& MeshBoneReductionModule = FModuleManager::Get().LoadModuleChecked("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; } bool IsSupported() const { 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 LicenseFileContents; if (FFileHelper::LoadFileToArray(LicenseFileContents, *FPaths::Combine(*DllPath, TEXT("license.dat")), FILEREAD_Silent) && LicenseFileContents.Num() > 0) { LicenseData = (const char*)LicenseFileContents.GetTypedData(); } 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; FMaterialCastingProperties() : bCastNormals(false) , bCastMaterials(false) { } }; // IMeshMerging interface virtual void BuildProxy( const TArray& InputMeshes, const TArray& 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(); //Create Image data where the diffuse data is stored SimplygonSDK::spImageData OutputDiffuseData = SDK->CreateImageData(); //Cast diffuse texture data { // 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); } //Cast 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); } //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(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(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& OutBoneTableIDs ) { TArray 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& BoneIDs ) { TArray 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 Influences; TArray Wedges; TArray Faces; TArray 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 PointNormals; TArray PointList; TArray 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( 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 DummyMap; DummyMap.AddUninitialized(MeshData.Points.Num()); for(int32 PointIdx = 0; PointIdx("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(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 SetMaterialChannel( const TArray& 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(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] = InSamples[TexelIndex].A; ImageColors->SetTuple(TexelIndex, Texel); } InSGMaterial->SetLayeredTextureImage(SGMaterialChannelName, 0, ImageData); InSGMaterial->SetLayeredTextureLevel(SGMaterialChannelName, 0, 0); } else { // handle uniform value } } //Material conversions bool CreateSGMaterialFromFlattenMaterial( const TArray& 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); SGMaterial->SetName(TCHAR_TO_ANSI(*FString::Printf(TEXT("Material%d"), MaterialIndex))); // Does current material have BaseColor? if (FlattenMaterial.DiffuseSamples.Num()) { SetMaterialChannel(FlattenMaterial.DiffuseSamples, FlattenMaterial.DiffuseSize, SGMaterial, SimplygonSDK::SG_MATERIAL_CHANNEL_DIFFUSE); } //Does current material have a normalmap? if (FlattenMaterial.NormalSamples.Num()) { OutCastProperties.bCastNormals = true; SetMaterialChannel(FlattenMaterial.NormalSamples, FlattenMaterial.NormalSize, SGMaterial, SimplygonSDK::SG_MATERIAL_CHANNEL_NORMALS); } OutSGMaterialTable->AddMaterial(SGMaterial); } return true; } void CreateFlattenMaterialFromSGMaterial( SimplygonSDK::spMaterialTable& InSGMaterialTable, MaterialExportUtils::FFlattenMaterial& OutMaterial) { //Get the diffuse data from SGMaterial SimplygonSDK::spImageData SGDiffuseData = InSGMaterialTable->GetMaterial(0)->GetLayeredTextureImage(SimplygonSDK::SG_MATERIAL_CHANNEL_DIFFUSE, 0); { SimplygonSDK::spUnsignedCharArray DiffuseImageColors = SimplygonSDK::SafeCast( SGDiffuseData->GetColors() ); OutMaterial.DiffuseSize.X = SGDiffuseData->GetXSize(); OutMaterial.DiffuseSize.Y = SGDiffuseData->GetYSize(); int32 TexelsCount = OutMaterial.DiffuseSize.X*OutMaterial.DiffuseSize.Y; //Fill diffuse texture with color data collected from simplygon OutMaterial.DiffuseSamples.SetNumUninitialized(TexelsCount); for (int32 TexelIndex = 0; TexelIndex < TexelsCount; ++TexelIndex) { uint8 DiffuseData[4]; DiffuseImageColors->GetTuple(TexelIndex, (unsigned char*)&DiffuseData); OutMaterial.DiffuseSamples[TexelIndex].R = DiffuseData[0]; OutMaterial.DiffuseSamples[TexelIndex].G = DiffuseData[1]; OutMaterial.DiffuseSamples[TexelIndex].B = DiffuseData[2]; OutMaterial.DiffuseSamples[TexelIndex].A = DiffuseData[3]; } } //Get the normal data from SGMaterial SimplygonSDK::spImageData SGNormalData = InSGMaterialTable->GetMaterial(0)->GetLayeredTextureImage(SimplygonSDK::SG_MATERIAL_CHANNEL_NORMALS, 0); if (SGNormalData) { SimplygonSDK::spUnsignedCharArray NormalImageColors = SimplygonSDK::SafeCast( SGNormalData->GetColors() ); OutMaterial.NormalSize.X = SGNormalData->GetXSize(); OutMaterial.NormalSize.Y = SGNormalData->GetYSize(); int32 TexelsCount = OutMaterial.NormalSize.X*OutMaterial.NormalSize.Y; //Fill normal texture with color data collected from simplygon OutMaterial.NormalSamples.SetNumUninitialized(TexelsCount); for (int32 TexelIndex = 0; TexelIndex < TexelsCount; ++TexelIndex) { uint8 NormalData[3]; NormalImageColors->GetTuple(TexelIndex, (unsigned char*)&NormalData); OutMaterial.NormalSamples[TexelIndex].R = NormalData[0]; OutMaterial.NormalSamples[TexelIndex].G = NormalData[1]; OutMaterial.NormalSamples[TexelIndex].B = NormalData[2]; OutMaterial.NormalSamples[TexelIndex].A = FColor::White.A; } } } }; TScopedPointer 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