Files
UnrealEngineUWP/Engine/Source/Developer/MeshBuilder/Private/SkeletalMeshBuilder.cpp
henry falconer 4510b454c4 Allow users to limit the number of bone influences per vertex below what the project supports.
BoneInfluencesLimit can be set on each LOD of an asset. DefaultBoneInfluencesLimit is a per-platform global default that can be set from the project settings.

This allows you to, for example, enable Unlimited Bone Influences on a project while ensuring that meshes don't use more than 8 influences by default, even if they've been imported with more.

#rb alexis.matte,josie.yang
#preflight 6380959cf514e1ded907774d

[CL 23265588 by henry falconer in ue5-main branch]
2022-11-25 08:32:25 -05:00

216 lines
8.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SkeletalMeshBuilder.h"
#include "Modules/ModuleManager.h"
#include "MeshBoneReduction.h"
#include "Engine/EngineTypes.h"
#include "Engine/SkeletalMesh.h"
#include "Engine/SkinnedAssetCommon.h"
#include "PhysicsEngine/BodySetup.h"
#include "MeshDescription.h"
#include "MeshAttributes.h"
#include "MeshDescriptionHelper.h"
#include "MeshBuild.h"
#include "Rendering/SkeletalMeshModel.h"
#include "Rendering/SkeletalMeshLODModel.h"
#include "GPUSkinVertexFactory.h"
#include "ThirdPartyBuildOptimizationHelper.h"
#include "Misc/ScopedSlowTask.h"
#include "LODUtilities.h"
#include "ClothingAsset.h"
#include "MeshUtilities.h"
#include "EditorFramework/AssetImportData.h"
#include "Interfaces/ITargetPlatform.h"
#include "Interfaces/ITargetPlatformManagerModule.h"
#include "Misc/CoreMisc.h"
DEFINE_LOG_CATEGORY(LogSkeletalMeshBuilder);
namespace SkeletalMeshBuilderOptimization
{
void CacheOptimizeIndexBuffer(TArray<uint16>& Indices)
{
BuildOptimizationThirdParty::CacheOptimizeIndexBuffer(Indices);
}
void CacheOptimizeIndexBuffer(TArray<uint32>& Indices)
{
BuildOptimizationThirdParty::CacheOptimizeIndexBuffer(Indices);
}
}
struct InfluenceMap
{
FORCEINLINE bool operator()(const float& A, const float& B) const
{
return B > A;
}
};
struct FSkeletalMeshVertInstanceIDAndZ
{
FVertexInstanceID Index;
float Z;
};
FSkeletalMeshBuilder::FSkeletalMeshBuilder()
{
}
bool FSkeletalMeshBuilder::Build(const FSkeletalMeshBuildParameters& SkeletalMeshBuildParameters)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSkeletalMeshBuilder::Build);
const int32 LODIndex = SkeletalMeshBuildParameters.LODIndex;
USkeletalMesh* SkeletalMesh = SkeletalMeshBuildParameters.SkeletalMesh;
check(SkeletalMesh->GetImportedModel());
check(SkeletalMesh->GetImportedModel()->LODModels.IsValidIndex(LODIndex));
check(SkeletalMesh->GetLODInfo(LODIndex) != nullptr);
const FSkeletalMeshLODInfo* LODInfo = SkeletalMesh->GetLODInfo(LODIndex);
//We want to backup in case the LODModel is regenerated, this data is use to validate in the UI if the ddc must be rebuild
const FString BackupBuildStringID = SkeletalMesh->GetImportedModel()->LODModels[LODIndex].BuildStringID;
const FReferenceSkeleton& RefSkeleton = SkeletalMesh->GetRefSkeleton();
FScopedSlowTask SlowTask(6.01f, NSLOCTEXT("SkeltalMeshBuilder", "BuildingSkeletalMeshLOD", "Building skeletal mesh LOD"));
SlowTask.MakeDialog();
//Prevent any PostEdit change during the build
FScopedSkeletalMeshPostEditChange ScopedPostEditChange(SkeletalMesh, false, false);
// Unbind any existing clothing assets before we reimport the geometry
TArray<ClothingAssetUtils::FClothingAssetMeshBinding> ClothingBindings;
FLODUtilities::UnbindClothingAndBackup(SkeletalMesh, ClothingBindings, LODIndex);
FSkeletalMeshImportData SkeletalMeshImportData;
int32 NumTextCoord = 1; //We need to send rendering at least one tex coord buffer
//This scope define where we can use the LODModel, after a reduction the LODModel must be requery since it is a new instance
{
FSkeletalMeshLODModel& BuildLODModel = SkeletalMesh->GetImportedModel()->LODModels[LODIndex];
//Load the imported data
SkeletalMesh->LoadLODImportedData(LODIndex, SkeletalMeshImportData);
TArray<FVector3f> LODPoints;
TArray<SkeletalMeshImportData::FMeshWedge> LODWedges;
TArray<SkeletalMeshImportData::FMeshFace> LODFaces;
TArray<SkeletalMeshImportData::FVertInfluence> LODInfluences;
TArray<int32> LODPointToRawMap;
SkeletalMeshImportData.CopyLODImportData(LODPoints, LODWedges, LODFaces, LODInfluences, LODPointToRawMap);
//Use the max because we need to have at least one texture coordinnate
NumTextCoord = FMath::Max<int32>(NumTextCoord, SkeletalMeshImportData.NumTexCoords);
//BaseLOD need to make sure the source data fit with the skeletalmesh materials array before using meshutilities.BuildSkeletalMesh
FLODUtilities::AdjustImportDataFaceMaterialIndex(SkeletalMesh->GetMaterials(), SkeletalMeshImportData.Materials, LODFaces, LODIndex);
//Build the skeletalmesh using mesh utilities module
IMeshUtilities::MeshBuildOptions Options;
Options.FillOptions(LODInfo->BuildSettings);
Options.TargetPlatform = SkeletalMeshBuildParameters.TargetPlatform;
//Force the normals or tangent in case the data is missing
Options.bComputeNormals |= !SkeletalMeshImportData.bHasNormals;
Options.bComputeTangents |= !SkeletalMeshImportData.bHasTangents;
IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>("MeshUtilities");
// Create skinning streams for NewModel.
SlowTask.EnterProgressFrame(1.0f);
MeshUtilities.BuildSkeletalMesh(
BuildLODModel,
SkeletalMesh->GetPathName(),
RefSkeleton,
LODInfluences,
LODWedges,
LODFaces,
LODPoints,
LODPointToRawMap,
Options
);
// Set texture coordinate count on the new model.
BuildLODModel.NumTexCoords = NumTextCoord;
//Cache the vertex/triangle count in the InlineReductionCacheData so we can know if the LODModel need reduction or not.
TArray<FInlineReductionCacheData>& InlineReductionCacheDatas = SkeletalMesh->GetImportedModel()->InlineReductionCacheDatas;
if (!InlineReductionCacheDatas.IsValidIndex(LODIndex))
{
InlineReductionCacheDatas.AddDefaulted((LODIndex + 1) - InlineReductionCacheDatas.Num());
}
if (ensure(InlineReductionCacheDatas.IsValidIndex(LODIndex)))
{
InlineReductionCacheDatas[LODIndex].SetCacheGeometryInfo(BuildLODModel);
}
//Re-Apply the user section changes, the UserSectionsData is map to original section and should match the builded LODModel
BuildLODModel.SyncronizeUserSectionsDataArray();
//Re-apply the morph target
SlowTask.EnterProgressFrame(1.0f, NSLOCTEXT("SkeltalMeshBuilder", "RebuildMorphTarget", "Rebuilding morph targets..."));
if (SkeletalMeshImportData.MorphTargetNames.Num() > 0)
{
FLODUtilities::BuildMorphTargets(SkeletalMesh, SkeletalMeshImportData, LODIndex, !Options.bComputeNormals, !Options.bComputeTangents, Options.bUseMikkTSpace, Options.OverlappingThresholds);
}
//Re-apply the alternate skinning it must be after the inline reduction
SlowTask.EnterProgressFrame(1.0f, NSLOCTEXT("SkeltalMeshBuilder", "RebuildAlternateSkinning", "Rebuilding alternate skinning..."));
const TArray<FSkinWeightProfileInfo>& SkinProfiles = SkeletalMesh->GetSkinWeightProfiles();
for (int32 SkinProfileIndex = 0; SkinProfileIndex < SkinProfiles.Num(); ++SkinProfileIndex)
{
const FSkinWeightProfileInfo& ProfileInfo = SkinProfiles[SkinProfileIndex];
FLODUtilities::UpdateAlternateSkinWeights(SkeletalMesh, ProfileInfo.Name, LODIndex, Options);
}
FSkeletalMeshUpdateContext UpdateContext;
UpdateContext.SkeletalMesh = SkeletalMesh;
//We are reduce ourself in this case we reduce ourself from the original data and return true.
if (SkeletalMesh->IsReductionActive(LODIndex))
{
SlowTask.EnterProgressFrame(1.0f, NSLOCTEXT("SkeltalMeshBuilder", "RegenerateLOD", "Regenerate LOD..."));
//Update the original reduction data since we just build a new LODModel.
if (LODInfo->ReductionSettings.BaseLOD == LODIndex && SkeletalMesh->IsLODImportedDataBuildAvailable(LODIndex))
{
if (LODIndex == 0)
{
SkeletalMesh->GetLODInfo(LODIndex)->SourceImportFilename = SkeletalMesh->GetAssetImportData()->GetFirstFilename();
}
}
FLODUtilities::SimplifySkeletalMeshLOD(UpdateContext, LODIndex, SkeletalMeshBuildParameters.TargetPlatform, false);
}
else
{
if (LODInfo->BonesToRemove.Num() > 0 && SkeletalMesh->GetSkeleton())
{
TArray<FName> BonesToRemove;
BonesToRemove.Reserve(LODInfo->BonesToRemove.Num());
for (const FBoneReference& BoneReference : LODInfo->BonesToRemove)
{
BonesToRemove.Add(BoneReference.BoneName);
}
MeshUtilities.RemoveBonesFromMesh(SkeletalMesh, LODIndex, &BonesToRemove);
}
}
}
FSkeletalMeshLODModel& LODModelAfterReduction = SkeletalMesh->GetImportedModel()->LODModels[LODIndex];
//Re-apply the clothing using the UserSectionsData, this will ensure we remap correctly the cloth if the reduction has change the number of sections
SlowTask.EnterProgressFrame(1.0f, NSLOCTEXT("SkeltalMeshBuilder", "RebuildClothing", "Rebuilding clothing..."));
FLODUtilities::RestoreClothingFromBackup(SkeletalMesh, ClothingBindings, LODIndex);
LODModelAfterReduction.SyncronizeUserSectionsDataArray();
LODModelAfterReduction.NumTexCoords = NumTextCoord;
LODModelAfterReduction.BuildStringID = BackupBuildStringID;
SlowTask.EnterProgressFrame(1.0f, NSLOCTEXT("SkeltalMeshBuilder", "RegenerateDependentLODs", "Regenerate Dependent LODs..."));
if (SkeletalMeshBuildParameters.bRegenDepLODs)
{
//Regenerate dependent LODs
FLODUtilities::RegenerateDependentLODs(SkeletalMesh, LODIndex, SkeletalMeshBuildParameters.TargetPlatform);
}
return true;
}