· 5 years ago · Sep 30, 2020, 09:22 AM
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#include "MeshUtilities.h"
4#include "MeshUtilitiesPrivate.h"
5#include "Misc/MessageDialog.h"
6#include "Misc/ScopeLock.h"
7#include "Containers/Ticker.h"
8#include "Misc/FeedbackContext.h"
9#include "Misc/ScopedSlowTask.h"
10#include "Misc/ConfigCacheIni.h"
11#include "Modules/ModuleManager.h"
12#include "UObject/Package.h"
13#include "Misc/PackageName.h"
14#include "Textures/SlateIcon.h"
15#include "Styling/SlateTypes.h"
16#include "Framework/Commands/UIAction.h"
17#include "Framework/Commands/UICommandList.h"
18#include "Framework/MultiBox/MultiBoxExtender.h"
19#include "Framework/MultiBox/MultiBoxBuilder.h"
20#include "ToolMenus.h"
21#include "SkeletalMeshToolMenuContext.h"
22#include "Components/MeshComponent.h"
23#include "RawIndexBuffer.h"
24#include "Components/StaticMeshComponent.h"
25#include "Components/ShapeComponent.h"
26#include "Engine/StaticMesh.h"
27#include "Materials/Material.h"
28#include "RawMesh.h"
29#include "StaticMeshResources.h"
30#include "MeshBuild.h"
31#include "ThirdPartyBuildOptimizationHelper.h"
32#include "SkeletalMeshTools.h"
33#include "Engine/SkeletalMesh.h"
34#include "Components/SkinnedMeshComponent.h"
35#include "ImageUtils.h"
36#include "LayoutUV.h"
37#include "mikktspace.h"
38#include "Misc/FbxErrors.h"
39#include "Components/SplineMeshComponent.h"
40#include "PhysicsEngine/ConvexElem.h"
41#include "PhysicsEngine/AggregateGeom.h"
42#include "PhysicsEngine/BodySetup.h"
43#include "MaterialUtilities.h"
44#include "IHierarchicalLODUtilities.h"
45#include "HierarchicalLODUtilitiesModule.h"
46#include "MeshBoneReduction.h"
47#include "MeshMergeData.h"
48#include "GPUSkinVertexFactory.h"
49#include "Developer/AssetTools/Public/IAssetTools.h"
50#include "Developer/AssetTools/Public/AssetToolsModule.h"
51#include "Materials/MaterialInstanceDynamic.h"
52#include "GameFramework/Character.h"
53#include "Components/CapsuleComponent.h"
54#include "Animation/DebugSkelMeshComponent.h"
55#include "Widgets/Text/STextBlock.h"
56#include "Widgets/Input/SComboButton.h"
57#include "Algo/Transform.h"
58#include "Rendering/SkeletalMeshModel.h"
59#include "Rendering/SkeletalMeshRenderData.h"
60
61#include "LandscapeProxy.h"
62#include "Landscape.h"
63#include "LandscapeHeightfieldCollisionComponent.h"
64#include "Engine/MeshMergeCullingVolume.h"
65
66#include "Toolkits/AssetEditorManager.h"
67#include "LevelEditor.h"
68#include "IAnimationBlueprintEditor.h"
69#include "IAnimationBlueprintEditorModule.h"
70#include "IAnimationEditor.h"
71#include "IAnimationEditorModule.h"
72#include "IContentBrowserSingleton.h"
73#include "ContentBrowserModule.h"
74#include "ISkeletalMeshEditor.h"
75#include "ISkeletalMeshEditorModule.h"
76#include "ISkeletonEditor.h"
77#include "ISkeletonEditorModule.h"
78#include "IPersonaToolkit.h"
79#include "Dialogs/DlgPickAssetPath.h"
80#include "SkeletalRenderPublic.h"
81#include "AssetRegistryModule.h"
82#include "Framework/Notifications/NotificationManager.h"
83#include "Widgets/Notifications/SNotificationList.h"
84#include "Engine/MeshSimplificationSettings.h"
85#include "Engine/SkeletalMeshSimplificationSettings.h"
86#include "Engine/ProxyLODMeshSimplificationSettings.h"
87
88#include "Editor/EditorPerProjectUserSettings.h"
89#include "IDetailCustomization.h"
90#include "EditorStyleSet.h"
91#include "PropertyEditorModule.h"
92#include "DetailLayoutBuilder.h"
93#include "DetailCategoryBuilder.h"
94#include "IDetailPropertyRow.h"
95#include "DetailWidgetRow.h"
96#include "OverlappingCorners.h"
97#include "MeshUtilitiesCommon.h"
98
99#include "StaticMeshAttributes.h"
100#include "StaticMeshOperations.h"
101
102#if WITH_EDITOR
103#include "Editor.h"
104#include "UnrealEdMisc.h"
105#include "Subsystems/AssetEditorSubsystem.h"
106#endif
107#include "MaterialBakingStructures.h"
108#include "IMaterialBakingModule.h"
109#include "MaterialOptions.h"
110
111
112#include "PrimitiveSceneProxy.h"
113#include "PrimitiveSceneInfo.h"
114#include "IMeshReductionManagerModule.h"
115#include "MeshMergeModule.h"
116
117
118DEFINE_LOG_CATEGORY(LogMeshUtilities);
119/*------------------------------------------------------------------------------
120MeshUtilities module.
121------------------------------------------------------------------------------*/
122
123// The version string is a GUID. If you make a change to mesh utilities that
124// causes meshes to be rebuilt you MUST generate a new GUID and replace this
125// string with it.
126
127#define MESH_UTILITIES_VER TEXT("228332BAE0224DD294E232B87D83948F")
128
129#define LOCTEXT_NAMESPACE "MeshUtils"
130
131IMPLEMENT_MODULE(FMeshUtilities, MeshUtilities);
132
133void FMeshUtilities::CacheOptimizeIndexBuffer(TArray<uint16>& Indices)
134{
135 BuildOptimizationThirdParty::CacheOptimizeIndexBuffer(Indices);
136}
137
138void FMeshUtilities::CacheOptimizeIndexBuffer(TArray<uint32>& Indices)
139{
140 BuildOptimizationThirdParty::CacheOptimizeIndexBuffer(Indices);
141}
142
143void FMeshUtilities::BuildSkeletalAdjacencyIndexBuffer(
144 const TArray<FSoftSkinVertex>& VertexBuffer,
145 const uint32 TexCoordCount,
146 const TArray<uint32>& Indices,
147 TArray<uint32>& OutPnAenIndices
148 )
149{
150 BuildOptimizationThirdParty::NvTriStripHelper::BuildSkeletalAdjacencyIndexBuffer(VertexBuffer, TexCoordCount, Indices, OutPnAenIndices);
151}
152
153void FMeshUtilities::CalcBoneVertInfos(USkeletalMesh* SkeletalMesh, TArray<FBoneVertInfo>& Infos, bool bOnlyDominant)
154{
155 SkeletalMeshTools::CalcBoneVertInfos(SkeletalMesh, Infos, bOnlyDominant);
156}
157
158// Helper function for ConvertMeshesToStaticMesh
159static void AddOrDuplicateMaterial(UMaterialInterface* InMaterialInterface, const FString& InPackageName, TArray<UMaterialInterface*>& OutMaterials)
160{
161 if (InMaterialInterface && !InMaterialInterface->GetOuter()->IsA<UPackage>())
162 {
163 // Convert runtime material instances to new concrete material instances
164 // Create new package
165 FString OriginalMaterialName = InMaterialInterface->GetName();
166 FString MaterialPath = FPackageName::GetLongPackagePath(InPackageName) / OriginalMaterialName;
167 FString MaterialName;
168 FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
169 AssetToolsModule.Get().CreateUniqueAssetName(MaterialPath, TEXT(""), MaterialPath, MaterialName);
170 UPackage* MaterialPackage = CreatePackage(NULL, *MaterialPath);
171
172 // Duplicate the object into the new package
173 UMaterialInterface* NewMaterialInterface = DuplicateObject<UMaterialInterface>(InMaterialInterface, MaterialPackage, *MaterialName);
174 NewMaterialInterface->SetFlags(RF_Public | RF_Standalone);
175
176 if (UMaterialInstanceDynamic* MaterialInstanceDynamic = Cast<UMaterialInstanceDynamic>(NewMaterialInterface))
177 {
178 UMaterialInstanceDynamic* OldMaterialInstanceDynamic = CastChecked<UMaterialInstanceDynamic>(InMaterialInterface);
179 MaterialInstanceDynamic->K2_CopyMaterialInstanceParameters(OldMaterialInstanceDynamic);
180 }
181
182 NewMaterialInterface->MarkPackageDirty();
183
184 FAssetRegistryModule::AssetCreated(NewMaterialInterface);
185
186 InMaterialInterface = NewMaterialInterface;
187 }
188
189 OutMaterials.Add(InMaterialInterface);
190}
191
192// Helper function for ConvertMeshesToStaticMesh
193template <typename ComponentType>
194static void ProcessMaterials(ComponentType* InComponent, const FString& InPackageName, TArray<UMaterialInterface*>& OutMaterials)
195{
196 const int32 NumMaterials = InComponent->GetNumMaterials();
197 for (int32 MaterialIndex = 0; MaterialIndex < NumMaterials; MaterialIndex++)
198 {
199 UMaterialInterface* MaterialInterface = InComponent->GetMaterial(MaterialIndex);
200 AddOrDuplicateMaterial(MaterialInterface, InPackageName, OutMaterials);
201 }
202}
203
204// Helper function for ConvertMeshesToStaticMesh
205static bool IsValidSkinnedMeshComponent(USkinnedMeshComponent* InComponent)
206{
207 return InComponent && InComponent->MeshObject && InComponent->IsVisible();
208}
209
210/** Helper struct for tracking validity of optional buffers */
211struct FRawMeshTracker
212{
213 FRawMeshTracker()
214 : bValidColors(false)
215 {
216 FMemory::Memset(bValidTexCoords, 0);
217 }
218
219 bool bValidTexCoords[MAX_MESH_TEXTURE_COORDS];
220 bool bValidColors;
221};
222
223// Helper function for ConvertMeshesToStaticMesh
224static void SkinnedMeshToRawMeshes(USkinnedMeshComponent* InSkinnedMeshComponent, int32 InOverallMaxLODs, const FMatrix& InComponentToWorld, const FString& InPackageName, TArray<FRawMeshTracker>& OutRawMeshTrackers, TArray<FRawMesh>& OutRawMeshes, TArray<UMaterialInterface*>& OutMaterials)
225{
226 const int32 BaseMaterialIndex = OutMaterials.Num();
227
228 // Export all LODs to raw meshes
229 const int32 NumLODs = InSkinnedMeshComponent->GetNumLODs();
230
231 for (int32 OverallLODIndex = 0; OverallLODIndex < InOverallMaxLODs; OverallLODIndex++)
232 {
233 int32 LODIndexRead = FMath::Min(OverallLODIndex, NumLODs - 1);
234
235 FRawMesh& RawMesh = OutRawMeshes[OverallLODIndex];
236 FRawMeshTracker& RawMeshTracker = OutRawMeshTrackers[OverallLODIndex];
237 const int32 BaseVertexIndex = RawMesh.VertexPositions.Num();
238
239 FSkeletalMeshLODInfo& SrcLODInfo = *(InSkinnedMeshComponent->SkeletalMesh->GetLODInfo(LODIndexRead));
240
241 // Get the CPU skinned verts for this LOD
242 TArray<FFinalSkinVertex> FinalVertices;
243 InSkinnedMeshComponent->GetCPUSkinnedVertices(FinalVertices, LODIndexRead);
244
245 FSkeletalMeshRenderData& SkeletalMeshRenderData = InSkinnedMeshComponent->MeshObject->GetSkeletalMeshRenderData();
246 FSkeletalMeshLODRenderData& LODData = SkeletalMeshRenderData.LODRenderData[LODIndexRead];
247
248 // Copy skinned vertex positions
249 for (int32 VertIndex = 0; VertIndex < FinalVertices.Num(); ++VertIndex)
250 {
251 RawMesh.VertexPositions.Add(InComponentToWorld.TransformPosition(FinalVertices[VertIndex].Position));
252 }
253
254 const uint32 NumTexCoords = FMath::Min(LODData.StaticVertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords(), (uint32)MAX_MESH_TEXTURE_COORDS);
255 const int32 NumSections = LODData.RenderSections.Num();
256 FRawStaticIndexBuffer16or32Interface& IndexBuffer = *LODData.MultiSizeIndexContainer.GetIndexBuffer();
257
258 for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
259 {
260 const FSkelMeshRenderSection& SkelMeshSection = LODData.RenderSections[SectionIndex];
261 if (InSkinnedMeshComponent->IsMaterialSectionShown(SkelMeshSection.MaterialIndex, LODIndexRead))
262 {
263 // Build 'wedge' info
264 const int32 NumWedges = SkelMeshSection.NumTriangles * 3;
265 for(int32 WedgeIndex = 0; WedgeIndex < NumWedges; WedgeIndex++)
266 {
267 const int32 VertexIndexForWedge = IndexBuffer.Get(SkelMeshSection.BaseIndex + WedgeIndex);
268
269 RawMesh.WedgeIndices.Add(BaseVertexIndex + VertexIndexForWedge);
270
271 const FFinalSkinVertex& SkinnedVertex = FinalVertices[VertexIndexForWedge];
272 const FVector TangentX = InComponentToWorld.TransformVector(SkinnedVertex.TangentX.ToFVector());
273 const FVector TangentZ = InComponentToWorld.TransformVector(SkinnedVertex.TangentZ.ToFVector());
274 const FVector4 UnpackedTangentZ = SkinnedVertex.TangentZ.ToFVector4();
275 const FVector TangentY = (TangentZ ^ TangentX).GetSafeNormal() * UnpackedTangentZ.W;
276
277 RawMesh.WedgeTangentX.Add(TangentX);
278 RawMesh.WedgeTangentY.Add(TangentY);
279 RawMesh.WedgeTangentZ.Add(TangentZ);
280
281 for (uint32 TexCoordIndex = 0; TexCoordIndex < MAX_MESH_TEXTURE_COORDS; TexCoordIndex++)
282 {
283 if (TexCoordIndex >= NumTexCoords)
284 {
285 RawMesh.WedgeTexCoords[TexCoordIndex].AddDefaulted();
286 }
287 else
288 {
289 RawMesh.WedgeTexCoords[TexCoordIndex].Add(LODData.StaticVertexBuffers.StaticMeshVertexBuffer.GetVertexUV(VertexIndexForWedge, TexCoordIndex));
290 RawMeshTracker.bValidTexCoords[TexCoordIndex] = true;
291 }
292 }
293
294 if (LODData.StaticVertexBuffers.ColorVertexBuffer.IsInitialized())
295 {
296 RawMesh.WedgeColors.Add(LODData.StaticVertexBuffers.ColorVertexBuffer.VertexColor(VertexIndexForWedge));
297 RawMeshTracker.bValidColors = true;
298 }
299 else
300 {
301 RawMesh.WedgeColors.Add(FColor::White);
302 }
303 }
304
305 int32 MaterialIndex = SkelMeshSection.MaterialIndex;
306 // use the remapping of material indices if there is a valid value
307 if (SrcLODInfo.LODMaterialMap.IsValidIndex(SectionIndex) && SrcLODInfo.LODMaterialMap[SectionIndex] != INDEX_NONE)
308 {
309 MaterialIndex = FMath::Clamp<int32>(SrcLODInfo.LODMaterialMap[SectionIndex], 0, InSkinnedMeshComponent->SkeletalMesh->Materials.Num());
310 }
311
312 // copy face info
313 for (uint32 TriIndex = 0; TriIndex < SkelMeshSection.NumTriangles; TriIndex++)
314 {
315 RawMesh.FaceMaterialIndices.Add(BaseMaterialIndex + MaterialIndex);
316 RawMesh.FaceSmoothingMasks.Add(0); // Assume this is ignored as bRecomputeNormals is false
317 }
318 }
319 }
320 }
321
322 ProcessMaterials<USkinnedMeshComponent>(InSkinnedMeshComponent, InPackageName, OutMaterials);
323}
324
325// Helper function for ConvertMeshesToStaticMesh
326static bool IsValidStaticMeshComponent(UStaticMeshComponent* InComponent)
327{
328 return InComponent && InComponent->GetStaticMesh() && InComponent->GetStaticMesh()->RenderData && InComponent->IsVisible();
329}
330
331// Helper function for ConvertMeshesToStaticMesh
332static void StaticMeshToRawMeshes(UStaticMeshComponent* InStaticMeshComponent, int32 InOverallMaxLODs, const FMatrix& InComponentToWorld, const FString& InPackageName, TArray<FRawMeshTracker>& OutRawMeshTrackers, TArray<FRawMesh>& OutRawMeshes, TArray<UMaterialInterface*>& OutMaterials)
333{
334 const int32 BaseMaterialIndex = OutMaterials.Num();
335
336 const int32 NumLODs = InStaticMeshComponent->GetStaticMesh()->RenderData->LODResources.Num();
337
338 for (int32 OverallLODIndex = 0; OverallLODIndex < InOverallMaxLODs; OverallLODIndex++)
339 {
340 int32 LODIndexRead = FMath::Min(OverallLODIndex, NumLODs - 1);
341
342 FRawMesh& RawMesh = OutRawMeshes[OverallLODIndex];
343 FRawMeshTracker& RawMeshTracker = OutRawMeshTrackers[OverallLODIndex];
344 const FStaticMeshLODResources& LODResource = InStaticMeshComponent->GetStaticMesh()->RenderData->LODResources[LODIndexRead];
345 const int32 BaseVertexIndex = RawMesh.VertexPositions.Num();
346
347 for (int32 VertIndex = 0; VertIndex < LODResource.GetNumVertices(); ++VertIndex)
348 {
349 RawMesh.VertexPositions.Add(InComponentToWorld.TransformPosition(LODResource.VertexBuffers.PositionVertexBuffer.VertexPosition((uint32)VertIndex)));
350 }
351
352 const FIndexArrayView IndexArrayView = LODResource.IndexBuffer.GetArrayView();
353 const FStaticMeshVertexBuffer& StaticMeshVertexBuffer = LODResource.VertexBuffers.StaticMeshVertexBuffer;
354 const int32 NumTexCoords = FMath::Min(StaticMeshVertexBuffer.GetNumTexCoords(), (uint32)MAX_MESH_TEXTURE_COORDS);
355 const int32 NumSections = LODResource.Sections.Num();
356
357 for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
358 {
359 const FStaticMeshSection& StaticMeshSection = LODResource.Sections[SectionIndex];
360
361 const int32 NumIndices = StaticMeshSection.NumTriangles * 3;
362 for (int32 IndexIndex = 0; IndexIndex < NumIndices; IndexIndex++)
363 {
364 int32 Index = IndexArrayView[StaticMeshSection.FirstIndex + IndexIndex];
365 RawMesh.WedgeIndices.Add(BaseVertexIndex + Index);
366
367 RawMesh.WedgeTangentX.Add(InComponentToWorld.TransformVector(StaticMeshVertexBuffer.VertexTangentX(Index)));
368 RawMesh.WedgeTangentY.Add(InComponentToWorld.TransformVector(StaticMeshVertexBuffer.VertexTangentY(Index)));
369 RawMesh.WedgeTangentZ.Add(InComponentToWorld.TransformVector(StaticMeshVertexBuffer.VertexTangentZ(Index)));
370
371 for (int32 TexCoordIndex = 0; TexCoordIndex < MAX_MESH_TEXTURE_COORDS; TexCoordIndex++)
372 {
373 if (TexCoordIndex >= NumTexCoords)
374 {
375 RawMesh.WedgeTexCoords[TexCoordIndex].AddDefaulted();
376 }
377 else
378 {
379 RawMesh.WedgeTexCoords[TexCoordIndex].Add(StaticMeshVertexBuffer.GetVertexUV(Index, TexCoordIndex));
380 RawMeshTracker.bValidTexCoords[TexCoordIndex] = true;
381 }
382 }
383
384 if (LODResource.VertexBuffers.ColorVertexBuffer.IsInitialized())
385 {
386 RawMesh.WedgeColors.Add(LODResource.VertexBuffers.ColorVertexBuffer.VertexColor(Index));
387 RawMeshTracker.bValidColors = true;
388 }
389 else
390 {
391 RawMesh.WedgeColors.Add(FColor::White);
392 }
393 }
394
395 // copy face info
396 for (uint32 TriIndex = 0; TriIndex < StaticMeshSection.NumTriangles; TriIndex++)
397 {
398 RawMesh.FaceMaterialIndices.Add(BaseMaterialIndex + StaticMeshSection.MaterialIndex);
399 RawMesh.FaceSmoothingMasks.Add(0); // Assume this is ignored as bRecomputeNormals is false
400 }
401 }
402 }
403
404 ProcessMaterials<UStaticMeshComponent>(InStaticMeshComponent, InPackageName, OutMaterials);
405}
406
407UStaticMesh* FMeshUtilities::ConvertMeshesToStaticMesh(const TArray<UMeshComponent*>& InMeshComponents, const FTransform& InRootTransform, const FString& InPackageName)
408{
409 UStaticMesh* StaticMesh = nullptr;
410
411 // Build a package name to use
412 FString MeshName;
413 FString PackageName;
414 if (InPackageName.IsEmpty())
415 {
416 FString NewNameSuggestion = FString(TEXT("StaticMesh"));
417 FString PackageNameSuggestion = FString(TEXT("/Game/Meshes/")) + NewNameSuggestion;
418 FString Name;
419 FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
420 AssetToolsModule.Get().CreateUniqueAssetName(PackageNameSuggestion, TEXT(""), PackageNameSuggestion, Name);
421
422 TSharedPtr<SDlgPickAssetPath> PickAssetPathWidget =
423 SNew(SDlgPickAssetPath)
424 .Title(LOCTEXT("ConvertToStaticMeshPickName", "Choose New StaticMesh Location"))
425 .DefaultAssetPath(FText::FromString(PackageNameSuggestion));
426
427 if (PickAssetPathWidget->ShowModal() == EAppReturnType::Ok)
428 {
429 // Get the full name of where we want to create the mesh asset.
430 PackageName = PickAssetPathWidget->GetFullAssetPath().ToString();
431 MeshName = FPackageName::GetLongPackageAssetName(PackageName);
432
433 // Check if the user inputed a valid asset name, if they did not, give it the generated default name
434 if (MeshName.IsEmpty())
435 {
436 // Use the defaults that were already generated.
437 PackageName = PackageNameSuggestion;
438 MeshName = *Name;
439 }
440 }
441 }
442 else
443 {
444 PackageName = InPackageName;
445 MeshName = *FPackageName::GetLongPackageAssetName(PackageName);
446 }
447
448 if(!PackageName.IsEmpty() && !MeshName.IsEmpty())
449 {
450 TArray<FRawMesh> RawMeshes;
451 TArray<UMaterialInterface*> Materials;
452
453 TArray<FRawMeshTracker> RawMeshTrackers;
454
455 FMatrix WorldToRoot = InRootTransform.ToMatrixWithScale().Inverse();
456
457 // first do a pass to determine the max LOD level we will be combining meshes into
458 int32 OverallMaxLODs = 0;
459 for (UMeshComponent* MeshComponent : InMeshComponents)
460 {
461 USkinnedMeshComponent* SkinnedMeshComponent = Cast<USkinnedMeshComponent>(MeshComponent);
462 UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(MeshComponent);
463
464 if (IsValidSkinnedMeshComponent(SkinnedMeshComponent))
465 {
466 OverallMaxLODs = FMath::Max(SkinnedMeshComponent->MeshObject->GetSkeletalMeshRenderData().LODRenderData.Num(), OverallMaxLODs);
467 }
468 else if(IsValidStaticMeshComponent(StaticMeshComponent))
469 {
470 OverallMaxLODs = FMath::Max(StaticMeshComponent->GetStaticMesh()->RenderData->LODResources.Num(), OverallMaxLODs);
471 }
472 }
473
474 // Resize raw meshes to accommodate the number of LODs we will need
475 RawMeshes.SetNum(OverallMaxLODs);
476 RawMeshTrackers.SetNum(OverallMaxLODs);
477
478 // Export all visible components
479 for (UMeshComponent* MeshComponent : InMeshComponents)
480 {
481 FMatrix ComponentToWorld = MeshComponent->GetComponentTransform().ToMatrixWithScale() * WorldToRoot;
482
483 USkinnedMeshComponent* SkinnedMeshComponent = Cast<USkinnedMeshComponent>(MeshComponent);
484 UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(MeshComponent);
485
486 if (IsValidSkinnedMeshComponent(SkinnedMeshComponent))
487 {
488 SkinnedMeshToRawMeshes(SkinnedMeshComponent, OverallMaxLODs, ComponentToWorld, PackageName, RawMeshTrackers, RawMeshes, Materials);
489 }
490 else if (IsValidStaticMeshComponent(StaticMeshComponent))
491 {
492 StaticMeshToRawMeshes(StaticMeshComponent, OverallMaxLODs, ComponentToWorld, PackageName, RawMeshTrackers, RawMeshes, Materials);
493 }
494 }
495
496 uint32 MaxInUseTextureCoordinate = 0;
497
498 // scrub invalid vert color & tex coord data
499 check(RawMeshes.Num() == RawMeshTrackers.Num());
500 for (int32 RawMeshIndex = 0; RawMeshIndex < RawMeshes.Num(); RawMeshIndex++)
501 {
502 if (!RawMeshTrackers[RawMeshIndex].bValidColors)
503 {
504 RawMeshes[RawMeshIndex].WedgeColors.Empty();
505 }
506
507 for (uint32 TexCoordIndex = 0; TexCoordIndex < MAX_MESH_TEXTURE_COORDS; TexCoordIndex++)
508 {
509 if (!RawMeshTrackers[RawMeshIndex].bValidTexCoords[TexCoordIndex])
510 {
511 RawMeshes[RawMeshIndex].WedgeTexCoords[TexCoordIndex].Empty();
512 }
513 else
514 {
515 // Store first texture coordinate index not in use
516 MaxInUseTextureCoordinate = FMath::Max(MaxInUseTextureCoordinate, TexCoordIndex);
517 }
518 }
519 }
520
521 // Check if we got some valid data.
522 bool bValidData = false;
523 for (FRawMesh& RawMesh : RawMeshes)
524 {
525 if (RawMesh.IsValidOrFixable())
526 {
527 bValidData = true;
528 break;
529 }
530 }
531
532 if (bValidData)
533 {
534 // Then find/create it.
535 UPackage* Package = CreatePackage(NULL, *PackageName);
536 check(Package);
537
538 // Create StaticMesh object
539 StaticMesh = NewObject<UStaticMesh>(Package, *MeshName, RF_Public | RF_Standalone);
540 StaticMesh->InitResources();
541
542 StaticMesh->LightingGuid = FGuid::NewGuid();
543
544 // Determine which texture coordinate map should be used for storing/generating the lightmap UVs
545 const uint32 LightMapIndex = FMath::Min(MaxInUseTextureCoordinate + 1, (uint32)MAX_MESH_TEXTURE_COORDS - 1);
546
547 // Add source to new StaticMesh
548 for (FRawMesh& RawMesh : RawMeshes)
549 {
550 if (RawMesh.IsValidOrFixable())
551 {
552 FStaticMeshSourceModel& SrcModel = StaticMesh->AddSourceModel();
553 SrcModel.BuildSettings.bRecomputeNormals = false;
554 SrcModel.BuildSettings.bRecomputeTangents = false;
555 SrcModel.BuildSettings.bRemoveDegenerates = true;
556 SrcModel.BuildSettings.bUseHighPrecisionTangentBasis = false;
557 SrcModel.BuildSettings.bUseFullPrecisionUVs = false;
558 SrcModel.BuildSettings.bGenerateLightmapUVs = true;
559 SrcModel.BuildSettings.SrcLightmapIndex = 0;
560 SrcModel.BuildSettings.DstLightmapIndex = LightMapIndex;
561 SrcModel.SaveRawMesh(RawMesh);
562 }
563 }
564
565 // Copy materials to new mesh
566 for(UMaterialInterface* Material : Materials)
567 {
568 StaticMesh->StaticMaterials.Add(FStaticMaterial(Material));
569 }
570
571 //Set the Imported version before calling the build
572 StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion;
573
574 // Set light map coordinate index to match DstLightmapIndex
575 StaticMesh->LightMapCoordinateIndex = LightMapIndex;
576
577 // setup section info map
578 for (int32 RawMeshLODIndex = 0; RawMeshLODIndex < RawMeshes.Num(); RawMeshLODIndex++)
579 {
580 const FRawMesh& RawMesh = RawMeshes[RawMeshLODIndex];
581 TArray<int32> UniqueMaterialIndices;
582 for (int32 MaterialIndex : RawMesh.FaceMaterialIndices)
583 {
584 UniqueMaterialIndices.AddUnique(MaterialIndex);
585 }
586
587 int32 SectionIndex = 0;
588 for (int32 UniqueMaterialIndex : UniqueMaterialIndices)
589 {
590 StaticMesh->GetSectionInfoMap().Set(RawMeshLODIndex, SectionIndex, FMeshSectionInfo(UniqueMaterialIndex));
591 SectionIndex++;
592 }
593 }
594 StaticMesh->GetOriginalSectionInfoMap().CopyFrom(StaticMesh->GetSectionInfoMap());
595
596 // Build mesh from source
597 StaticMesh->Build(false);
598 StaticMesh->PostEditChange();
599
600 StaticMesh->MarkPackageDirty();
601
602 // Notify asset registry of new asset
603 FAssetRegistryModule::AssetCreated(StaticMesh);
604
605 // Display notification so users can quickly access the mesh
606 if (GIsEditor)
607 {
608 FNotificationInfo Info(FText::Format(LOCTEXT("SkeletalMeshConverted", "Successfully Converted Mesh"), FText::FromString(StaticMesh->GetName())));
609 Info.ExpireDuration = 8.0f;
610 Info.bUseLargeFont = false;
611 Info.Hyperlink = FSimpleDelegate::CreateLambda([=]() { GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAssets(TArray<UObject*>({ StaticMesh })); });
612 Info.HyperlinkText = FText::Format(LOCTEXT("OpenNewAnimationHyperlink", "Open {0}"), FText::FromString(StaticMesh->GetName()));
613 TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
614 if ( Notification.IsValid() )
615 {
616 Notification->SetCompletionState( SNotificationItem::CS_Success );
617 }
618 }
619 }
620 }
621
622 return StaticMesh;
623}
624
625/**
626* Builds a renderable skeletal mesh LOD model. Note that the array of chunks
627* will be destroyed during this process!
628* @param LODModel Upon return contains a renderable skeletal mesh LOD model.
629* @param RefSkeleton The reference skeleton associated with the model.
630* @param Chunks Skinned mesh chunks from which to build the renderable model.
631* @param PointToOriginalMap Maps a vertex's RawPointIdx to its index at import time.
632*/
633void FMeshUtilities::BuildSkeletalModelFromChunks(FSkeletalMeshLODModel& LODModel, const FReferenceSkeleton& RefSkeleton, TArray<FSkinnedMeshChunk*>& Chunks, const TArray<int32>& PointToOriginalMap)
634{
635#if WITH_EDITORONLY_DATA
636 // Clear out any data currently held in the LOD model.
637 LODModel.Sections.Empty();
638 LODModel.NumVertices = 0;
639 LODModel.IndexBuffer.Empty();
640
641 // Setup the section and chunk arrays on the model.
642 for (int32 ChunkIndex = 0; ChunkIndex < Chunks.Num(); ++ChunkIndex)
643 {
644 FSkinnedMeshChunk* SrcChunk = Chunks[ChunkIndex];
645
646 FSkelMeshSection& Section = *new(LODModel.Sections) FSkelMeshSection();
647 Section.MaterialIndex = SrcChunk->MaterialIndex;
648 Exchange(Section.BoneMap, SrcChunk->BoneMap);
649
650 Section.OriginalDataSectionIndex = SrcChunk->OriginalSectionIndex;
651 Section.ChunkedParentSectionIndex = SrcChunk->ParentChunkSectionIndex;
652
653 // Update the active bone indices on the LOD model.
654 for (int32 BoneIndex = 0; BoneIndex < Section.BoneMap.Num(); ++BoneIndex)
655 {
656 LODModel.ActiveBoneIndices.AddUnique(Section.BoneMap[BoneIndex]);
657 }
658 }
659
660 // ensure parent exists with incoming active bone indices, and the result should be sorted
661 RefSkeleton.EnsureParentsExistAndSort(LODModel.ActiveBoneIndices);
662
663 // Reset 'final vertex to import vertex' map info
664 LODModel.MeshToImportVertexMap.Empty();
665 LODModel.MaxImportVertex = 0;
666
667 // Keep track of index mapping to chunk vertex offsets
668 TArray< TArray<uint32> > VertexIndexRemap;
669 VertexIndexRemap.Empty(LODModel.Sections.Num());
670 // Pack the chunk vertices into a single vertex buffer.
671 TArray<uint32> RawPointIndices;
672 LODModel.NumVertices = 0;
673
674 int32 PrevMaterialIndex = -1;
675 int32 CurrentChunkBaseVertexIndex = -1; // base vertex index for all chunks of the same material
676 int32 CurrentChunkVertexCount = -1; // total vertex count for all chunks of the same material
677 int32 CurrentVertexIndex = 0; // current vertex index added to the index buffer for all chunks of the same material
678
679 // rearrange the vert order to minimize the data fetched by the GPU
680 for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
681 {
682 if (IsInGameThread())
683 {
684 GWarn->StatusUpdate(SectionIndex, LODModel.Sections.Num(), NSLOCTEXT("UnrealEd", "ProcessingSections", "Processing Sections"));
685 }
686
687 FSkinnedMeshChunk* SrcChunk = Chunks[SectionIndex];
688 FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
689 TArray<FSoftSkinBuildVertex>& ChunkVertices = SrcChunk->Vertices;
690 TArray<uint32>& ChunkIndices = SrcChunk->Indices;
691
692 // Reorder the section index buffer for better vertex cache efficiency.
693 CacheOptimizeIndexBuffer(ChunkIndices);
694
695 // Calculate the number of triangles in the section. Note that CacheOptimize may change the number of triangles in the index buffer!
696 Section.NumTriangles = ChunkIndices.Num() / 3;
697 TArray<FSoftSkinBuildVertex> OriginalVertices;
698 Exchange(ChunkVertices, OriginalVertices);
699 ChunkVertices.AddUninitialized(OriginalVertices.Num());
700
701 TArray<int32> IndexCache;
702 IndexCache.AddUninitialized(ChunkVertices.Num());
703 FMemory::Memset(IndexCache.GetData(), INDEX_NONE, IndexCache.Num() * IndexCache.GetTypeSize());
704 int32 NextAvailableIndex = 0;
705 // Go through the indices and assign them new values that are coherent where possible
706 for (int32 Index = 0; Index < ChunkIndices.Num(); Index++)
707 {
708 const int32 OriginalIndex = ChunkIndices[Index];
709 const int32 CachedIndex = IndexCache[OriginalIndex];
710
711 if (CachedIndex == INDEX_NONE)
712 {
713 // No new index has been allocated for this existing index, assign a new one
714 ChunkIndices[Index] = NextAvailableIndex;
715 // Mark what this index has been assigned to
716 IndexCache[OriginalIndex] = NextAvailableIndex;
717 NextAvailableIndex++;
718 }
719 else
720 {
721 // Reuse an existing index assignment
722 ChunkIndices[Index] = CachedIndex;
723 }
724 // Reorder the vertices based on the new index assignment
725 ChunkVertices[ChunkIndices[Index]] = OriginalVertices[OriginalIndex];
726 }
727 }
728
729 // Build the arrays of rigid and soft vertices on the model's chunks.
730 for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
731 {
732 FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
733 TArray<FSoftSkinBuildVertex>& ChunkVertices = Chunks[SectionIndex]->Vertices;
734
735 if (IsInGameThread())
736 {
737 // Only update status if in the game thread. When importing morph targets, this function can run in another thread
738 GWarn->StatusUpdate(SectionIndex, LODModel.Sections.Num(), NSLOCTEXT("UnrealEd", "ProcessingChunks", "Processing Chunks"));
739 }
740
741 CurrentVertexIndex = 0;
742 CurrentChunkVertexCount = 0;
743 PrevMaterialIndex = Section.MaterialIndex;
744
745 // Calculate the offset to this chunk's vertices in the vertex buffer.
746 Section.BaseVertexIndex = CurrentChunkBaseVertexIndex = LODModel.NumVertices;
747
748 // Update the size of the vertex buffer.
749 LODModel.NumVertices += ChunkVertices.Num();
750
751 // Separate the section's vertices into rigid and soft vertices.
752 TArray<uint32>& ChunkVertexIndexRemap = *new(VertexIndexRemap)TArray<uint32>();
753 ChunkVertexIndexRemap.AddUninitialized(ChunkVertices.Num());
754
755 for (int32 VertexIndex = 0; VertexIndex < ChunkVertices.Num(); VertexIndex++)
756 {
757 const FSoftSkinBuildVertex& SoftVertex = ChunkVertices[VertexIndex];
758
759 FSoftSkinVertex NewVertex;
760 NewVertex.Position = SoftVertex.Position;
761 NewVertex.TangentX = SoftVertex.TangentX;
762 NewVertex.TangentY = SoftVertex.TangentY;
763 NewVertex.TangentZ = SoftVertex.TangentZ;
764 FMemory::Memcpy(NewVertex.UVs, SoftVertex.UVs, sizeof(FVector2D)*MAX_TEXCOORDS);
765 NewVertex.Color = SoftVertex.Color;
766 for (int32 i = 0; i < MAX_TOTAL_INFLUENCES; ++i)
767 {
768 // it only adds to the bone map if it has weight on it
769 // BoneMap contains only the bones that has influence with weight of >0.f
770 // so here, just make sure it is included before setting the data
771 if (Section.BoneMap.IsValidIndex(SoftVertex.InfluenceBones[i]))
772 {
773 NewVertex.InfluenceBones[i] = SoftVertex.InfluenceBones[i];
774 NewVertex.InfluenceWeights[i] = SoftVertex.InfluenceWeights[i];
775 }
776 }
777 Section.SoftVertices.Add(NewVertex);
778 ChunkVertexIndexRemap[VertexIndex] = (uint32)(Section.BaseVertexIndex + CurrentVertexIndex);
779 CurrentVertexIndex++;
780 // add the index to the original wedge point source of this vertex
781 RawPointIndices.Add(SoftVertex.PointWedgeIdx);
782 // Also remember import index
783 const int32 RawVertIndex = PointToOriginalMap[SoftVertex.PointWedgeIdx];
784 LODModel.MeshToImportVertexMap.Add(RawVertIndex);
785 LODModel.MaxImportVertex = FMath::Max<float>(LODModel.MaxImportVertex, RawVertIndex);
786 }
787
788 // update NumVertices
789 Section.NumVertices = Section.SoftVertices.Num();
790
791 // update max bone influences
792 Section.CalcMaxBoneInfluences();
793 Section.CalcUse16BitBoneIndex();
794
795 // Log info about the chunk.
796 UE_LOG(LogSkeletalMesh, Log, TEXT("Section %u: %u vertices, %u active bones"),
797 SectionIndex,
798 Section.GetNumVertices(),
799 Section.BoneMap.Num()
800 );
801 }
802
803 // Copy raw point indices to LOD model.
804 LODModel.RawPointIndices.RemoveBulkData();
805 if (RawPointIndices.Num())
806 {
807 LODModel.RawPointIndices.Lock(LOCK_READ_WRITE);
808 void* Dest = LODModel.RawPointIndices.Realloc(RawPointIndices.Num());
809 FMemory::Memcpy(Dest, RawPointIndices.GetData(), LODModel.RawPointIndices.GetBulkDataSize());
810 LODModel.RawPointIndices.Unlock();
811 }
812
813 // Finish building the sections.
814 for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
815 {
816 FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
817
818 const TArray<uint32>& SectionIndices = Chunks[SectionIndex]->Indices;
819
820 Section.BaseIndex = LODModel.IndexBuffer.Num();
821 const int32 NumIndices = SectionIndices.Num();
822 const TArray<uint32>& SectionVertexIndexRemap = VertexIndexRemap[SectionIndex];
823 for (int32 Index = 0; Index < NumIndices; Index++)
824 {
825 uint32 VertexIndex = SectionVertexIndexRemap[SectionIndices[Index]];
826 LODModel.IndexBuffer.Add(VertexIndex);
827 }
828 }
829
830 // Free the skinned mesh chunks which are no longer needed.
831 for (int32 i = 0; i < Chunks.Num(); ++i)
832 {
833 delete Chunks[i];
834 Chunks[i] = NULL;
835 }
836 Chunks.Empty();
837
838 // Compute the required bones for this model.
839 USkeletalMesh::CalculateRequiredBones(LODModel, RefSkeleton, NULL);
840#endif // #if WITH_EDITORONLY_DATA
841}
842
843/*------------------------------------------------------------------------------
844Common functionality.
845------------------------------------------------------------------------------*/
846
847static int32 ComputeNumTexCoords(FRawMesh const& RawMesh, int32 MaxSupportedTexCoords)
848{
849 int32 NumWedges = RawMesh.WedgeIndices.Num();
850 int32 NumTexCoords = 0;
851 for (int32 TexCoordIndex = 0; TexCoordIndex < MAX_MESH_TEXTURE_COORDS; ++TexCoordIndex)
852 {
853 if (RawMesh.WedgeTexCoords[TexCoordIndex].Num() != NumWedges)
854 {
855 break;
856 }
857 NumTexCoords++;
858 }
859 return FMath::Min(NumTexCoords, MaxSupportedTexCoords);
860}
861
862static inline FVector GetPositionForWedge(FRawMesh const& Mesh, int32 WedgeIndex)
863{
864 int32 VertexIndex = Mesh.WedgeIndices[WedgeIndex];
865 return Mesh.VertexPositions[VertexIndex];
866}
867
868struct FMeshEdgeDef
869{
870 int32 Vertices[2];
871 int32 Faces[2];
872};
873
874/**
875* This helper class builds the edge list for a mesh. It uses a hash of vertex
876* positions to edges sharing that vertex to remove the n^2 searching of all
877* previously added edges. This class is templatized so it can be used with
878* either static mesh or skeletal mesh vertices
879*/
880template <class VertexClass> class TEdgeBuilder
881{
882protected:
883 /**
884 * The list of indices to build the edge data from
885 */
886 const TArray<uint32>& Indices;
887 /**
888 * The array of verts for vertex position comparison
889 */
890 const TArray<VertexClass>& Vertices;
891 /**
892 * The array of edges to create
893 */
894 TArray<FMeshEdgeDef>& Edges;
895 /**
896 * List of edges that start with a given vertex
897 */
898 TMultiMap<FVector, FMeshEdgeDef*> VertexToEdgeList;
899
900 /**
901 * This function determines whether a given edge matches or not. It must be
902 * provided by derived classes since they have the specific information that
903 * this class doesn't know about (vertex info, influences, etc)
904 *
905 * @param Index1 The first index of the edge being checked
906 * @param Index2 The second index of the edge
907 * @param OtherEdge The edge to compare. Was found via the map
908 *
909 * @return true if the edge is a match, false otherwise
910 */
911 virtual bool DoesEdgeMatch(int32 Index1, int32 Index2, FMeshEdgeDef* OtherEdge) = 0;
912
913 /**
914 * Searches the list of edges to see if this one matches an existing and
915 * returns a pointer to it if it does
916 *
917 * @param Index1 the first index to check for
918 * @param Index2 the second index to check for
919 *
920 * @return NULL if no edge was found, otherwise the edge that was found
921 */
922 inline FMeshEdgeDef* FindOppositeEdge(int32 Index1, int32 Index2)
923 {
924 FMeshEdgeDef* Edge = NULL;
925 // Search the hash for a corresponding vertex
926 WorkingEdgeList.Reset();
927 VertexToEdgeList.MultiFind(Vertices[Index2].Position, WorkingEdgeList);
928 // Now search through the array for a match or not
929 for (int32 EdgeIndex = 0; EdgeIndex < WorkingEdgeList.Num() && Edge == NULL;
930 EdgeIndex++)
931 {
932 FMeshEdgeDef* OtherEdge = WorkingEdgeList[EdgeIndex];
933 // See if this edge matches the passed in edge
934 if (OtherEdge != NULL && DoesEdgeMatch(Index1, Index2, OtherEdge))
935 {
936 // We have a match
937 Edge = OtherEdge;
938 }
939 }
940 return Edge;
941 }
942
943 /**
944 * Updates an existing edge if found or adds the new edge to the list
945 *
946 * @param Index1 the first index in the edge
947 * @param Index2 the second index in the edge
948 * @param Triangle the triangle that this edge was found in
949 */
950 inline void AddEdge(int32 Index1, int32 Index2, int32 Triangle)
951 {
952 // If this edge matches another then just fill the other triangle
953 // otherwise add it
954 FMeshEdgeDef* OtherEdge = FindOppositeEdge(Index1, Index2);
955 if (OtherEdge == NULL)
956 {
957 // Add a new edge to the array
958 int32 EdgeIndex = Edges.AddZeroed();
959 Edges[EdgeIndex].Vertices[0] = Index1;
960 Edges[EdgeIndex].Vertices[1] = Index2;
961 Edges[EdgeIndex].Faces[0] = Triangle;
962 Edges[EdgeIndex].Faces[1] = -1;
963 // Also add this edge to the hash for faster searches
964 // NOTE: This relies on the array never being realloced!
965 VertexToEdgeList.Add(Vertices[Index1].Position, &Edges[EdgeIndex]);
966 }
967 else
968 {
969 OtherEdge->Faces[1] = Triangle;
970 }
971 }
972
973public:
974 /**
975 * Initializes the values for the code that will build the mesh edge list
976 */
977 TEdgeBuilder(const TArray<uint32>& InIndices,
978 const TArray<VertexClass>& InVertices,
979 TArray<FMeshEdgeDef>& OutEdges) :
980 Indices(InIndices), Vertices(InVertices), Edges(OutEdges)
981 {
982 // Presize the array so that there are no extra copies being done
983 // when adding edges to it
984 Edges.Empty(Indices.Num());
985 }
986
987 /**
988 * Virtual dtor
989 */
990 virtual ~TEdgeBuilder(){}
991
992
993 /**
994 * Uses a hash of indices to edge lists so that it can avoid the n^2 search
995 * through the full edge list
996 */
997 void FindEdges(void)
998 {
999 // @todo Handle something other than trilists when building edges
1000 int32 TriangleCount = Indices.Num() / 3;
1001 int32 EdgeCount = 0;
1002 // Work through all triangles building the edges
1003 for (int32 Triangle = 0; Triangle < TriangleCount; Triangle++)
1004 {
1005 // Determine the starting index
1006 int32 TriangleIndex = Triangle * 3;
1007 // Get the indices for the triangle
1008 int32 Index1 = Indices[TriangleIndex];
1009 int32 Index2 = Indices[TriangleIndex + 1];
1010 int32 Index3 = Indices[TriangleIndex + 2];
1011 // Add the first to second edge
1012 AddEdge(Index1, Index2, Triangle);
1013 // Now add the second to third
1014 AddEdge(Index2, Index3, Triangle);
1015 // Add the third to first edge
1016 AddEdge(Index3, Index1, Triangle);
1017 }
1018 }
1019
1020private:
1021 TArray<FMeshEdgeDef*> WorkingEdgeList;
1022};
1023
1024/**
1025* This is the static mesh specific version for finding edges
1026*/
1027class FStaticMeshEdgeBuilder : public TEdgeBuilder<FStaticMeshBuildVertex>
1028{
1029public:
1030 /**
1031 * Constructor that passes all work to the parent class
1032 */
1033 FStaticMeshEdgeBuilder(const TArray<uint32>& InIndices,
1034 const TArray<FStaticMeshBuildVertex>& InVertices,
1035 TArray<FMeshEdgeDef>& OutEdges) :
1036 TEdgeBuilder<FStaticMeshBuildVertex>(InIndices, InVertices, OutEdges)
1037 {
1038 }
1039
1040 /**
1041 * This function determines whether a given edge matches or not for a static mesh
1042 *
1043 * @param Index1 The first index of the edge being checked
1044 * @param Index2 The second index of the edge
1045 * @param OtherEdge The edge to compare. Was found via the map
1046 *
1047 * @return true if the edge is a match, false otherwise
1048 */
1049 bool DoesEdgeMatch(int32 Index1, int32 Index2, FMeshEdgeDef* OtherEdge)
1050 {
1051 return Vertices[OtherEdge->Vertices[1]].Position == Vertices[Index1].Position &&
1052 OtherEdge->Faces[1] == -1;
1053 }
1054};
1055
1056static void ComputeTriangleTangents(
1057 const TArray<FVector>& InVertices,
1058 const TArray<uint32>& InIndices,
1059 const TArray<FVector2D>& InUVs,
1060 TArray<FVector>& OutTangentX,
1061 TArray<FVector>& OutTangentY,
1062 TArray<FVector>& OutTangentZ,
1063 float ComparisonThreshold
1064 )
1065{
1066 const int32 NumTriangles = InIndices.Num() / 3;
1067 OutTangentX.Empty(NumTriangles);
1068 OutTangentY.Empty(NumTriangles);
1069 OutTangentZ.Empty(NumTriangles);
1070
1071 //Currently GetSafeNormal do not support 0.0f threshold properly
1072 float RealComparisonThreshold = FMath::Max(ComparisonThreshold, FLT_MIN);
1073
1074 for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; TriangleIndex++)
1075 {
1076 int32 UVIndex = 0;
1077
1078 FVector P[3];
1079 for (int32 i = 0; i < 3; ++i)
1080 {
1081 P[i] = InVertices[InIndices[TriangleIndex * 3 + i]];
1082 }
1083
1084 const FVector Normal = ((P[1] - P[2]) ^ (P[0] - P[2])).GetSafeNormal(RealComparisonThreshold);
1085 //Avoid doing orthonormal vector from a degenerated triangle.
1086 if (!Normal.IsNearlyZero(FLT_MIN))
1087 {
1088 FMatrix ParameterToLocal(
1089 FPlane(P[1].X - P[0].X, P[1].Y - P[0].Y, P[1].Z - P[0].Z, 0),
1090 FPlane(P[2].X - P[0].X, P[2].Y - P[0].Y, P[2].Z - P[0].Z, 0),
1091 FPlane(P[0].X, P[0].Y, P[0].Z, 0),
1092 FPlane(0, 0, 0, 1)
1093 );
1094
1095 const FVector2D T1 = InUVs[TriangleIndex * 3 + 0];
1096 const FVector2D T2 = InUVs[TriangleIndex * 3 + 1];
1097 const FVector2D T3 = InUVs[TriangleIndex * 3 + 2];
1098
1099 FMatrix ParameterToTexture(
1100 FPlane(T2.X - T1.X, T2.Y - T1.Y, 0, 0),
1101 FPlane(T3.X - T1.X, T3.Y - T1.Y, 0, 0),
1102 FPlane(T1.X, T1.Y, 1, 0),
1103 FPlane(0, 0, 0, 1)
1104 );
1105
1106 // Use InverseSlow to catch singular matrices. Inverse can miss this sometimes.
1107 const FMatrix TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal;
1108
1109 OutTangentX.Add(TextureToLocal.TransformVector(FVector(1, 0, 0)).GetSafeNormal());
1110 OutTangentY.Add(TextureToLocal.TransformVector(FVector(0, 1, 0)).GetSafeNormal());
1111 OutTangentZ.Add(Normal);
1112
1113 FVector::CreateOrthonormalBasis(
1114 OutTangentX[TriangleIndex],
1115 OutTangentY[TriangleIndex],
1116 OutTangentZ[TriangleIndex]
1117 );
1118
1119 if (OutTangentX[TriangleIndex].IsNearlyZero() || OutTangentX[TriangleIndex].ContainsNaN()
1120 || OutTangentY[TriangleIndex].IsNearlyZero() || OutTangentY[TriangleIndex].ContainsNaN())
1121 {
1122 OutTangentX[TriangleIndex] = FVector::ZeroVector;
1123 OutTangentY[TriangleIndex] = FVector::ZeroVector;
1124 }
1125
1126 if (OutTangentZ[TriangleIndex].IsNearlyZero() || OutTangentZ[TriangleIndex].ContainsNaN())
1127 {
1128 OutTangentZ[TriangleIndex] = FVector::ZeroVector;
1129 }
1130 }
1131 else
1132 {
1133 //Add zero tangents and normal for this triangle, this is like weighting it to zero when we compute the vertex normal
1134 //But we need the triangle to correctly connect other neighbourg triangles
1135 OutTangentX.Add(FVector::ZeroVector);
1136 OutTangentY.Add(FVector::ZeroVector);
1137 OutTangentZ.Add(FVector::ZeroVector);
1138 }
1139 }
1140
1141 check(OutTangentX.Num() == NumTriangles);
1142 check(OutTangentY.Num() == NumTriangles);
1143 check(OutTangentZ.Num() == NumTriangles);
1144}
1145
1146static void ComputeTriangleTangents(
1147 TArray<FVector>& OutTangentX,
1148 TArray<FVector>& OutTangentY,
1149 TArray<FVector>& OutTangentZ,
1150 FRawMesh const& RawMesh,
1151 float ComparisonThreshold
1152 )
1153{
1154 ComputeTriangleTangents(RawMesh.VertexPositions, RawMesh.WedgeIndices, RawMesh.WedgeTexCoords[0], OutTangentX, OutTangentY, OutTangentZ, ComparisonThreshold);
1155
1156 /*int32 NumTriangles = RawMesh.WedgeIndices.Num() / 3;
1157 TriangleTangentX.Empty(NumTriangles);
1158 TriangleTangentY.Empty(NumTriangles);
1159 TriangleTangentZ.Empty(NumTriangles);
1160
1161 for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; TriangleIndex++)
1162 {
1163 int32 UVIndex = 0;
1164
1165 FVector P[3];
1166 for (int32 i = 0; i < 3; ++i)
1167 {
1168 P[i] = GetPositionForWedge(RawMesh, TriangleIndex * 3 + i);
1169 }
1170
1171 const FVector Normal = ((P[1] - P[2]) ^ (P[0] - P[2])).GetSafeNormal(ComparisonThreshold);
1172 FMatrix ParameterToLocal(
1173 FPlane(P[1].X - P[0].X, P[1].Y - P[0].Y, P[1].Z - P[0].Z, 0),
1174 FPlane(P[2].X - P[0].X, P[2].Y - P[0].Y, P[2].Z - P[0].Z, 0),
1175 FPlane(P[0].X, P[0].Y, P[0].Z, 0),
1176 FPlane(0, 0, 0, 1)
1177 );
1178
1179 FVector2D T1 = RawMesh.WedgeTexCoords[UVIndex][TriangleIndex * 3 + 0];
1180 FVector2D T2 = RawMesh.WedgeTexCoords[UVIndex][TriangleIndex * 3 + 1];
1181 FVector2D T3 = RawMesh.WedgeTexCoords[UVIndex][TriangleIndex * 3 + 2];
1182 FMatrix ParameterToTexture(
1183 FPlane(T2.X - T1.X, T2.Y - T1.Y, 0, 0),
1184 FPlane(T3.X - T1.X, T3.Y - T1.Y, 0, 0),
1185 FPlane(T1.X, T1.Y, 1, 0),
1186 FPlane(0, 0, 0, 1)
1187 );
1188
1189 // Use InverseSlow to catch singular matrices. Inverse can miss this sometimes.
1190 const FMatrix TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal;
1191
1192 TriangleTangentX.Add(TextureToLocal.TransformVector(FVector(1, 0, 0)).GetSafeNormal());
1193 TriangleTangentY.Add(TextureToLocal.TransformVector(FVector(0, 1, 0)).GetSafeNormal());
1194 TriangleTangentZ.Add(Normal);
1195
1196 FVector::CreateOrthonormalBasis(
1197 TriangleTangentX[TriangleIndex],
1198 TriangleTangentY[TriangleIndex],
1199 TriangleTangentZ[TriangleIndex]
1200 );
1201 }
1202
1203 check(TriangleTangentX.Num() == NumTriangles);
1204 check(TriangleTangentY.Num() == NumTriangles);
1205 check(TriangleTangentZ.Num() == NumTriangles);*/
1206}
1207
1208/**
1209* Create a table that maps the corner of each face to its overlapping corners.
1210* @param OutOverlappingCorners - Maps a corner index to the indices of all overlapping corners.
1211* @param InVertices - Triangle vertex positions for the mesh for which to compute overlapping corners.
1212* @param InIndices - Triangle indices for the mesh for which to compute overlapping corners.
1213* @param ComparisonThreshold - Positions are considered equal if all absolute differences between their X, Y and Z coordinates are less or equal to this value.
1214*/
1215void FMeshUtilities::FindOverlappingCorners(
1216 FOverlappingCorners& OutOverlappingCorners,
1217 const TArray<FVector>& InVertices,
1218 const TArray<uint32>& InIndices,
1219 float ComparisonThreshold) const
1220{
1221 OutOverlappingCorners = FOverlappingCorners(InVertices, InIndices, ComparisonThreshold);
1222}
1223
1224/**
1225* Create a table that maps the corner of each face to its overlapping corners.
1226* @param OutOverlappingCorners - Maps a corner index to the indices of all overlapping corners.
1227* @param RawMesh - The mesh for which to compute overlapping corners.
1228* @param ComparisonThreshold - Positions are considered equal if all absolute differences between their X, Y and Z coordinates are less or equal to this value.
1229*/
1230void FMeshUtilities::FindOverlappingCorners(
1231 FOverlappingCorners& OutOverlappingCorners,
1232 FRawMesh const& RawMesh,
1233 float ComparisonThreshold
1234 ) const
1235{
1236 OutOverlappingCorners = FOverlappingCorners(RawMesh.VertexPositions, RawMesh.WedgeIndices, ComparisonThreshold);
1237}
1238
1239/**
1240* Smoothing group interpretation helper structure.
1241*/
1242struct FFanFace
1243{
1244 int32 FaceIndex;
1245 int32 LinkedVertexIndex;
1246 bool bFilled;
1247 bool bBlendTangents;
1248 bool bBlendNormals;
1249};
1250
1251static void ComputeTangents(
1252 const TArray<FVector>& InVertices,
1253 const TArray<uint32>& InIndices,
1254 const TArray<FVector2D>& InUVs,
1255 const TArray<uint32>& SmoothingGroupIndices,
1256 const FOverlappingCorners& OverlappingCorners,
1257 TArray<FVector>& OutTangentX,
1258 TArray<FVector>& OutTangentY,
1259 TArray<FVector>& OutTangentZ,
1260 const uint32 TangentOptions
1261 )
1262{
1263 bool bBlendOverlappingNormals = (TangentOptions & ETangentOptions::BlendOverlappingNormals) != 0;
1264 bool bIgnoreDegenerateTriangles = (TangentOptions & ETangentOptions::IgnoreDegenerateTriangles) != 0;
1265 float ComparisonThreshold = bIgnoreDegenerateTriangles ? THRESH_POINTS_ARE_SAME : 0.0f;
1266
1267 // Compute per-triangle tangents.
1268 TArray<FVector> TriangleTangentX;
1269 TArray<FVector> TriangleTangentY;
1270 TArray<FVector> TriangleTangentZ;
1271
1272 ComputeTriangleTangents(
1273 InVertices,
1274 InIndices,
1275 InUVs,
1276 TriangleTangentX,
1277 TriangleTangentY,
1278 TriangleTangentZ,
1279 bIgnoreDegenerateTriangles ? SMALL_NUMBER : FLT_MIN
1280 );
1281
1282 // Declare these out here to avoid reallocations.
1283 TArray<FFanFace> RelevantFacesForCorner[3];
1284 TArray<int32> AdjacentFaces;
1285
1286 int32 NumWedges = InIndices.Num();
1287 int32 NumFaces = NumWedges / 3;
1288
1289 // Allocate storage for tangents if none were provided.
1290 if (OutTangentX.Num() != NumWedges)
1291 {
1292 OutTangentX.Empty(NumWedges);
1293 OutTangentX.AddZeroed(NumWedges);
1294 }
1295 if (OutTangentY.Num() != NumWedges)
1296 {
1297 OutTangentY.Empty(NumWedges);
1298 OutTangentY.AddZeroed(NumWedges);
1299 }
1300 if (OutTangentZ.Num() != NumWedges)
1301 {
1302 OutTangentZ.Empty(NumWedges);
1303 OutTangentZ.AddZeroed(NumWedges);
1304 }
1305
1306 for (int32 FaceIndex = 0; FaceIndex < NumFaces; FaceIndex++)
1307 {
1308 int32 WedgeOffset = FaceIndex * 3;
1309 FVector CornerPositions[3];
1310 FVector CornerTangentX[3];
1311 FVector CornerTangentY[3];
1312 FVector CornerTangentZ[3];
1313
1314 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
1315 {
1316 CornerTangentX[CornerIndex] = FVector::ZeroVector;
1317 CornerTangentY[CornerIndex] = FVector::ZeroVector;
1318 CornerTangentZ[CornerIndex] = FVector::ZeroVector;
1319 CornerPositions[CornerIndex] = InVertices[InIndices[WedgeOffset + CornerIndex]];
1320 RelevantFacesForCorner[CornerIndex].Reset();
1321 }
1322
1323 // Don't process degenerate triangles.
1324 if (PointsEqual(CornerPositions[0], CornerPositions[1], ComparisonThreshold)
1325 || PointsEqual(CornerPositions[0], CornerPositions[2], ComparisonThreshold)
1326 || PointsEqual(CornerPositions[1], CornerPositions[2], ComparisonThreshold))
1327 {
1328 continue;
1329 }
1330
1331 // No need to process triangles if tangents already exist.
1332 bool bCornerHasTangents[3] = { 0 };
1333 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
1334 {
1335 bCornerHasTangents[CornerIndex] = !OutTangentX[WedgeOffset + CornerIndex].IsZero()
1336 && !OutTangentY[WedgeOffset + CornerIndex].IsZero()
1337 && !OutTangentZ[WedgeOffset + CornerIndex].IsZero();
1338 }
1339 if (bCornerHasTangents[0] && bCornerHasTangents[1] && bCornerHasTangents[2])
1340 {
1341 continue;
1342 }
1343
1344 // Calculate smooth vertex normals.
1345 float Determinant = FVector::Triple(
1346 TriangleTangentX[FaceIndex],
1347 TriangleTangentY[FaceIndex],
1348 TriangleTangentZ[FaceIndex]
1349 );
1350
1351 // Start building a list of faces adjacent to this face.
1352 AdjacentFaces.Reset();
1353 for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
1354 {
1355 int32 ThisCornerIndex = WedgeOffset + CornerIndex;
1356 const TArray<int32>& DupVerts = OverlappingCorners.FindIfOverlapping(ThisCornerIndex);
1357 for (int32 k = 0, nk = DupVerts.Num() ; k < nk; k++)
1358 {
1359 AdjacentFaces.Add(DupVerts[k] / 3);
1360 }
1361 if (DupVerts.Num() == 0)
1362 {
1363 AdjacentFaces.Add(ThisCornerIndex / 3); // I am a "dup" of myself
1364 }
1365 }
1366
1367 // We need to sort these here because the criteria for point equality is
1368 // exact, so we must ensure the exact same order for all dups.
1369 AdjacentFaces.Sort();
1370
1371 // Process adjacent faces
1372 int32 LastIndex = -1;
1373 for (int32 OtherFaceIndex : AdjacentFaces)
1374 {
1375 if (LastIndex == OtherFaceIndex)
1376 {
1377 continue;
1378 }
1379
1380 LastIndex = OtherFaceIndex;
1381
1382 for (int32 OurCornerIndex = 0; OurCornerIndex < 3; ++OurCornerIndex)
1383 {
1384 if (bCornerHasTangents[OurCornerIndex])
1385 continue;
1386
1387 FFanFace NewFanFace;
1388 int32 CommonIndexCount = 0;
1389
1390 // Check for vertices in common.
1391 if (FaceIndex == OtherFaceIndex)
1392 {
1393 CommonIndexCount = 3;
1394 NewFanFace.LinkedVertexIndex = OurCornerIndex;
1395 }
1396 else
1397 {
1398 // Check matching vertices against main vertex .
1399 for (int32 OtherCornerIndex = 0; OtherCornerIndex < 3; ++OtherCornerIndex)
1400 {
1401 if(CornerPositions[OurCornerIndex].Equals(InVertices[InIndices[OtherFaceIndex * 3 + OtherCornerIndex]], ComparisonThreshold))
1402 {
1403 CommonIndexCount++;
1404 NewFanFace.LinkedVertexIndex = OtherCornerIndex;
1405 }
1406 }
1407 }
1408
1409 // Add if connected by at least one point. Smoothing matches are considered later.
1410 if (CommonIndexCount > 0)
1411 {
1412 NewFanFace.FaceIndex = OtherFaceIndex;
1413 NewFanFace.bFilled = (OtherFaceIndex == FaceIndex); // Starter face for smoothing floodfill.
1414 NewFanFace.bBlendTangents = NewFanFace.bFilled;
1415 NewFanFace.bBlendNormals = NewFanFace.bFilled;
1416 RelevantFacesForCorner[OurCornerIndex].Add(NewFanFace);
1417 }
1418 }
1419 }
1420
1421 // Find true relevance of faces for a vertex normal by traversing
1422 // smoothing-group-compatible connected triangle fans around common vertices.
1423 for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
1424 {
1425 if (bCornerHasTangents[CornerIndex])
1426 continue;
1427
1428 int32 NewConnections;
1429 do
1430 {
1431 NewConnections = 0;
1432 for (int32 OtherFaceIdx = 0, ni = RelevantFacesForCorner[CornerIndex].Num() ; OtherFaceIdx < ni; ++OtherFaceIdx)
1433 {
1434 FFanFace& OtherFace = RelevantFacesForCorner[CornerIndex][OtherFaceIdx];
1435 // The vertex' own face is initially the only face with bFilled == true.
1436 if (OtherFace.bFilled)
1437 {
1438 for (int32 NextFaceIndex = 0, nk = RelevantFacesForCorner[CornerIndex].Num() ; NextFaceIndex < nk ; ++NextFaceIndex)
1439 {
1440 FFanFace& NextFace = RelevantFacesForCorner[CornerIndex][NextFaceIndex];
1441 if (!NextFace.bFilled) // && !NextFace.bBlendTangents)
1442 {
1443 if ((NextFaceIndex != OtherFaceIdx)
1444 && (SmoothingGroupIndices[NextFace.FaceIndex] & SmoothingGroupIndices[OtherFace.FaceIndex]))
1445 {
1446 int32 CommonVertices = 0;
1447 int32 CommonTangentVertices = 0;
1448 int32 CommonNormalVertices = 0;
1449 for (int32 OtherCornerIndex = 0; OtherCornerIndex < 3; ++OtherCornerIndex)
1450 {
1451 for (int32 NextCornerIndex = 0; NextCornerIndex < 3; ++NextCornerIndex)
1452 {
1453 int32 NextVertexIndex = InIndices[NextFace.FaceIndex * 3 + NextCornerIndex];
1454 int32 OtherVertexIndex = InIndices[OtherFace.FaceIndex * 3 + OtherCornerIndex];
1455 if (PointsEqual(
1456 InVertices[NextVertexIndex],
1457 InVertices[OtherVertexIndex],
1458 ComparisonThreshold))
1459 {
1460 CommonVertices++;
1461
1462
1463 const FVector2D& UVOne = InUVs[NextFace.FaceIndex * 3 + NextCornerIndex];
1464 const FVector2D& UVTwo = InUVs[OtherFace.FaceIndex * 3 + OtherCornerIndex];
1465
1466 if (UVsEqual(UVOne, UVTwo))
1467 {
1468 CommonTangentVertices++;
1469 }
1470 if (bBlendOverlappingNormals
1471 || NextVertexIndex == OtherVertexIndex)
1472 {
1473 CommonNormalVertices++;
1474 }
1475 }
1476 }
1477 }
1478 // Flood fill faces with more than one common vertices which must be touching edges.
1479 if (CommonVertices > 1)
1480 {
1481 NextFace.bFilled = true;
1482 NextFace.bBlendNormals = (CommonNormalVertices > 1);
1483 NewConnections++;
1484
1485 // Only blend tangents if there is no UV seam along the edge with this face.
1486 if (OtherFace.bBlendTangents && CommonTangentVertices > 1)
1487 {
1488 float OtherDeterminant = FVector::Triple(
1489 TriangleTangentX[NextFace.FaceIndex],
1490 TriangleTangentY[NextFace.FaceIndex],
1491 TriangleTangentZ[NextFace.FaceIndex]
1492 );
1493 if ((Determinant * OtherDeterminant) > 0.0f)
1494 {
1495 NextFace.bBlendTangents = true;
1496 }
1497 }
1498 }
1499 }
1500 }
1501 }
1502 }
1503 }
1504 } while (NewConnections > 0);
1505 }
1506
1507 // Vertex normal construction.
1508 for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
1509 {
1510 if (bCornerHasTangents[CornerIndex])
1511 {
1512 CornerTangentX[CornerIndex] = OutTangentX[WedgeOffset + CornerIndex];
1513 CornerTangentY[CornerIndex] = OutTangentY[WedgeOffset + CornerIndex];
1514 CornerTangentZ[CornerIndex] = OutTangentZ[WedgeOffset + CornerIndex];
1515 }
1516 else
1517 {
1518 for (int32 RelevantFaceIdx = 0; RelevantFaceIdx < RelevantFacesForCorner[CornerIndex].Num(); ++RelevantFaceIdx)
1519 {
1520 FFanFace const& RelevantFace = RelevantFacesForCorner[CornerIndex][RelevantFaceIdx];
1521 if (RelevantFace.bFilled)
1522 {
1523 int32 OtherFaceIndex = RelevantFace.FaceIndex;
1524 if (RelevantFace.bBlendTangents)
1525 {
1526 CornerTangentX[CornerIndex] += TriangleTangentX[OtherFaceIndex];
1527 CornerTangentY[CornerIndex] += TriangleTangentY[OtherFaceIndex];
1528 }
1529 if (RelevantFace.bBlendNormals)
1530 {
1531 CornerTangentZ[CornerIndex] += TriangleTangentZ[OtherFaceIndex];
1532 }
1533 }
1534 }
1535 if (!OutTangentX[WedgeOffset + CornerIndex].IsZero())
1536 {
1537 CornerTangentX[CornerIndex] = OutTangentX[WedgeOffset + CornerIndex];
1538 }
1539 if (!OutTangentY[WedgeOffset + CornerIndex].IsZero())
1540 {
1541 CornerTangentY[CornerIndex] = OutTangentY[WedgeOffset + CornerIndex];
1542 }
1543 if (!OutTangentZ[WedgeOffset + CornerIndex].IsZero())
1544 {
1545 CornerTangentZ[CornerIndex] = OutTangentZ[WedgeOffset + CornerIndex];
1546 }
1547 }
1548 }
1549
1550 // Normalization.
1551 for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
1552 {
1553 CornerTangentX[CornerIndex].Normalize();
1554 CornerTangentY[CornerIndex].Normalize();
1555 CornerTangentZ[CornerIndex].Normalize();
1556
1557 // Gram-Schmidt orthogonalization
1558 CornerTangentY[CornerIndex] -= CornerTangentX[CornerIndex] * (CornerTangentX[CornerIndex] | CornerTangentY[CornerIndex]);
1559 CornerTangentY[CornerIndex].Normalize();
1560
1561 CornerTangentX[CornerIndex] -= CornerTangentZ[CornerIndex] * (CornerTangentZ[CornerIndex] | CornerTangentX[CornerIndex]);
1562 CornerTangentX[CornerIndex].Normalize();
1563 CornerTangentY[CornerIndex] -= CornerTangentZ[CornerIndex] * (CornerTangentZ[CornerIndex] | CornerTangentY[CornerIndex]);
1564 CornerTangentY[CornerIndex].Normalize();
1565 }
1566
1567 // Copy back to the mesh.
1568 for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
1569 {
1570 OutTangentX[WedgeOffset + CornerIndex] = CornerTangentX[CornerIndex];
1571 OutTangentY[WedgeOffset + CornerIndex] = CornerTangentY[CornerIndex];
1572 OutTangentZ[WedgeOffset + CornerIndex] = CornerTangentZ[CornerIndex];
1573 }
1574 }
1575
1576 check(OutTangentX.Num() == NumWedges);
1577 check(OutTangentY.Num() == NumWedges);
1578 check(OutTangentZ.Num() == NumWedges);
1579}
1580
1581
1582static void ComputeTangents(
1583 FRawMesh& RawMesh,
1584 const FOverlappingCorners& OverlappingCorners,
1585 uint32 TangentOptions
1586 )
1587{
1588 ComputeTangents(RawMesh.VertexPositions, RawMesh.WedgeIndices, RawMesh.WedgeTexCoords[0], RawMesh.FaceSmoothingMasks, OverlappingCorners, RawMesh.WedgeTangentX, RawMesh.WedgeTangentY, RawMesh.WedgeTangentZ, TangentOptions);
1589}
1590
1591/*------------------------------------------------------------------------------
1592MikkTSpace for computing tangents.
1593------------------------------------------------------------------------------*/
1594class MikkTSpace_Mesh
1595{
1596public:
1597 const TArray<FVector>& Vertices;
1598 const TArray<uint32>& Indices;
1599 const TArray<FVector2D>& UVs;
1600
1601 TArray<FVector>& TangentsX; //Reference to newly created tangents list.
1602 TArray<FVector>& TangentsY; //Reference to newly created bitangents list.
1603 TArray<FVector>& TangentsZ; //Reference to computed normals, will be empty otherwise.
1604
1605 MikkTSpace_Mesh(
1606 const TArray<FVector> &InVertices,
1607 const TArray<uint32> &InIndices,
1608 const TArray<FVector2D> &InUVs,
1609 TArray<FVector> &InVertexTangentsX,
1610 TArray<FVector> &InVertexTangentsY,
1611 TArray<FVector> &InVertexTangentsZ
1612 )
1613 :
1614 Vertices(InVertices),
1615 Indices(InIndices),
1616 UVs(InUVs),
1617 TangentsX(InVertexTangentsX),
1618 TangentsY(InVertexTangentsY),
1619 TangentsZ(InVertexTangentsZ)
1620 {
1621 }
1622};
1623
1624static int MikkGetNumFaces(const SMikkTSpaceContext* Context)
1625{
1626 MikkTSpace_Mesh *UserData = (MikkTSpace_Mesh*)(Context->m_pUserData);
1627 return UserData->Indices.Num() / 3;
1628}
1629
1630static int MikkGetNumVertsOfFace(const SMikkTSpaceContext* Context, const int FaceIdx)
1631{
1632 // All of our meshes are triangles.
1633 return 3;
1634}
1635
1636static void MikkGetPosition(const SMikkTSpaceContext* Context, float Position[3], const int FaceIdx, const int VertIdx)
1637{
1638 MikkTSpace_Mesh *UserData = (MikkTSpace_Mesh*)(Context->m_pUserData);
1639 FVector VertexPosition = UserData->Vertices[ UserData->Indices[FaceIdx * 3 + VertIdx] ];
1640 Position[0] = VertexPosition.X;
1641 Position[1] = VertexPosition.Y;
1642 Position[2] = VertexPosition.Z;
1643}
1644
1645static void MikkGetNormal(const SMikkTSpaceContext* Context, float Normal[3], const int FaceIdx, const int VertIdx)
1646{
1647 MikkTSpace_Mesh *UserData = (MikkTSpace_Mesh*)(Context->m_pUserData);
1648 FVector &VertexNormal = UserData->TangentsZ[FaceIdx * 3 + VertIdx];
1649 for (int32 i = 0; i < 3; ++i)
1650 {
1651 Normal[i] = VertexNormal[i];
1652 }
1653}
1654
1655static void MikkSetTSpaceBasic(const SMikkTSpaceContext* Context, const float Tangent[3], const float BitangentSign, const int FaceIdx, const int VertIdx)
1656{
1657 MikkTSpace_Mesh *UserData = (MikkTSpace_Mesh*)(Context->m_pUserData);
1658 FVector &VertexTangent = UserData->TangentsX[FaceIdx * 3 + VertIdx];
1659 for (int32 i = 0; i < 3; ++i)
1660 {
1661 VertexTangent[i] = Tangent[i];
1662 }
1663 FVector Bitangent = BitangentSign * FVector::CrossProduct(UserData->TangentsZ[FaceIdx * 3 + VertIdx], VertexTangent);
1664 FVector &VertexBitangent = UserData->TangentsY[FaceIdx * 3 + VertIdx];
1665 for (int32 i = 0; i < 3; ++i)
1666 {
1667 VertexBitangent[i] = -Bitangent[i];
1668 }
1669}
1670
1671static void MikkGetTexCoord(const SMikkTSpaceContext* Context, float UV[2], const int FaceIdx, const int VertIdx)
1672{
1673 MikkTSpace_Mesh *UserData = (MikkTSpace_Mesh*)(Context->m_pUserData);
1674 const FVector2D &TexCoord = UserData->UVs[FaceIdx * 3 + VertIdx];
1675 UV[0] = TexCoord.X;
1676 UV[1] = TexCoord.Y;
1677}
1678
1679// MikkTSpace implementations for skeletal meshes, where tangents/bitangents are ultimately derived from lists of attributes.
1680
1681// Holder for skeletal data to be passed to MikkTSpace.
1682// Holds references to the wedge, face and points vectors that BuildSkeletalMesh is given.
1683// Holds reference to the calculated normals array, which will be fleshed out if they've been calculated.
1684// Holds reference to the newly created tangent and bitangent arrays, which MikkTSpace will fleshed out if required.
1685class MikkTSpace_Skeletal_Mesh
1686{
1687public:
1688 const TArray<SkeletalMeshImportData::FMeshWedge> &wedges; //Reference to wedge list.
1689 const TArray<SkeletalMeshImportData::FMeshFace> &faces; //Reference to face list. Also contains normal/tangent/bitanget/UV coords for each vertex of the face.
1690 const TArray<FVector> &points; //Reference to position list.
1691 bool bComputeNormals; //Copy of bComputeNormals.
1692 TArray<FVector> &TangentsX; //Reference to newly created tangents list.
1693 TArray<FVector> &TangentsY; //Reference to newly created bitangents list.
1694 TArray<FVector> &TangentsZ; //Reference to computed normals, will be empty otherwise.
1695
1696 MikkTSpace_Skeletal_Mesh(
1697 const TArray<SkeletalMeshImportData::FMeshWedge> &Wedges,
1698 const TArray<SkeletalMeshImportData::FMeshFace> &Faces,
1699 const TArray<FVector> &Points,
1700 bool bInComputeNormals,
1701 TArray<FVector> &VertexTangentsX,
1702 TArray<FVector> &VertexTangentsY,
1703 TArray<FVector> &VertexTangentsZ
1704 )
1705 :
1706 wedges(Wedges),
1707 faces(Faces),
1708 points(Points),
1709 bComputeNormals(bInComputeNormals),
1710 TangentsX(VertexTangentsX),
1711 TangentsY(VertexTangentsY),
1712 TangentsZ(VertexTangentsZ)
1713 {
1714 }
1715};
1716
1717static int MikkGetNumFaces_Skeletal(const SMikkTSpaceContext* Context)
1718{
1719 MikkTSpace_Skeletal_Mesh *UserData = (MikkTSpace_Skeletal_Mesh*)(Context->m_pUserData);
1720 return UserData->faces.Num();
1721}
1722
1723static int MikkGetNumVertsOfFace_Skeletal(const SMikkTSpaceContext* Context, const int FaceIdx)
1724{
1725 // Confirmed?
1726 return 3;
1727}
1728
1729static void MikkGetPosition_Skeletal(const SMikkTSpaceContext* Context, float Position[3], const int FaceIdx, const int VertIdx)
1730{
1731 MikkTSpace_Skeletal_Mesh *UserData = (MikkTSpace_Skeletal_Mesh*)(Context->m_pUserData);
1732 const FVector &VertexPosition = UserData->points[UserData->wedges[UserData->faces[FaceIdx].iWedge[VertIdx]].iVertex];
1733 Position[0] = VertexPosition.X;
1734 Position[1] = VertexPosition.Y;
1735 Position[2] = VertexPosition.Z;
1736}
1737
1738static void MikkGetNormal_Skeletal(const SMikkTSpaceContext* Context, float Normal[3], const int FaceIdx, const int VertIdx)
1739{
1740 MikkTSpace_Skeletal_Mesh *UserData = (MikkTSpace_Skeletal_Mesh*)(Context->m_pUserData);
1741 // Get different normals depending on whether they've been calculated or not.
1742 if (UserData->bComputeNormals) {
1743 FVector &VertexNormal = UserData->TangentsZ[FaceIdx * 3 + VertIdx];
1744 Normal[0] = VertexNormal.X;
1745 Normal[1] = VertexNormal.Y;
1746 Normal[2] = VertexNormal.Z;
1747 }
1748 else
1749 {
1750 const FVector &VertexNormal = UserData->faces[FaceIdx].TangentZ[VertIdx];
1751 Normal[0] = VertexNormal.X;
1752 Normal[1] = VertexNormal.Y;
1753 Normal[2] = VertexNormal.Z;
1754 }
1755}
1756
1757static void MikkSetTSpaceBasic_Skeletal(const SMikkTSpaceContext* Context, const float Tangent[3], const float BitangentSign, const int FaceIdx, const int VertIdx)
1758{
1759 MikkTSpace_Skeletal_Mesh *UserData = (MikkTSpace_Skeletal_Mesh*)(Context->m_pUserData);
1760 FVector &VertexTangent = UserData->TangentsX[FaceIdx * 3 + VertIdx];
1761 VertexTangent.X = Tangent[0];
1762 VertexTangent.Y = Tangent[1];
1763 VertexTangent.Z = Tangent[2];
1764
1765 FVector Bitangent;
1766 // Get different normals depending on whether they've been calculated or not.
1767 if (UserData->bComputeNormals) {
1768 Bitangent = BitangentSign * FVector::CrossProduct(UserData->TangentsZ[FaceIdx * 3 + VertIdx], VertexTangent);
1769 }
1770 else
1771 {
1772 Bitangent = BitangentSign * FVector::CrossProduct(UserData->faces[FaceIdx].TangentZ[VertIdx], VertexTangent);
1773 }
1774 FVector &VertexBitangent = UserData->TangentsY[FaceIdx * 3 + VertIdx];
1775 // Switch the tangent space swizzle to X+Y-Z+ for legacy reasons.
1776 VertexBitangent.X = -Bitangent[0];
1777 VertexBitangent.Y = -Bitangent[1];
1778 VertexBitangent.Z = -Bitangent[2];
1779}
1780
1781static void MikkGetTexCoord_Skeletal(const SMikkTSpaceContext* Context, float UV[2], const int FaceIdx, const int VertIdx)
1782{
1783 MikkTSpace_Skeletal_Mesh *UserData = (MikkTSpace_Skeletal_Mesh*)(Context->m_pUserData);
1784 const FVector2D &TexCoord = UserData->wedges[UserData->faces[FaceIdx].iWedge[VertIdx]].UVs[0];
1785 UV[0] = TexCoord.X;
1786 UV[1] = TexCoord.Y;
1787}
1788
1789static void ComputeNormals(
1790 const TArray<FVector>& InVertices,
1791 const TArray<uint32>& InIndices,
1792 const TArray<FVector2D>& InUVs,
1793 const TArray<uint32>& SmoothingGroupIndices,
1794 const FOverlappingCorners& OverlappingCorners,
1795 TArray<FVector>& OutTangentZ,
1796 const uint32 TangentOptions
1797 )
1798{
1799 bool bBlendOverlappingNormals = (TangentOptions & ETangentOptions::BlendOverlappingNormals) != 0;
1800 bool bIgnoreDegenerateTriangles = (TangentOptions & ETangentOptions::IgnoreDegenerateTriangles) != 0;
1801 float ComparisonThreshold = bIgnoreDegenerateTriangles ? THRESH_POINTS_ARE_SAME : 0.0f;
1802
1803 // Compute per-triangle tangents.
1804 TArray<FVector> TriangleTangentX;
1805 TArray<FVector> TriangleTangentY;
1806 TArray<FVector> TriangleTangentZ;
1807
1808 ComputeTriangleTangents(
1809 InVertices,
1810 InIndices,
1811 InUVs,
1812 TriangleTangentX,
1813 TriangleTangentY,
1814 TriangleTangentZ,
1815 bIgnoreDegenerateTriangles ? SMALL_NUMBER : FLT_MIN
1816 );
1817
1818 // Declare these out here to avoid reallocations.
1819 TArray<FFanFace> RelevantFacesForCorner[3];
1820// TArray<int32> AdjacentFaces;
1821
1822 int32 NumWedges = InIndices.Num();
1823 int32 NumFaces = NumWedges / 3;
1824
1825 // Allocate storage for tangents if none were provided, and calculate normals for MikkTSpace.
1826 if (OutTangentZ.Num() != NumWedges)
1827 {
1828 // normals are not included, so we should calculate them
1829 OutTangentZ.Empty(NumWedges);
1830 OutTangentZ.AddZeroed(NumWedges);
1831 }
1832
1833 // we need to calculate normals for MikkTSpace
1834 for (int32 FaceIndex = 0; FaceIndex < NumFaces; FaceIndex++)
1835 {
1836 int32 WedgeOffset = FaceIndex * 3;
1837 FVector CornerPositions[3];
1838 FVector CornerNormal[3];
1839
1840 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
1841 {
1842 CornerNormal[CornerIndex] = FVector::ZeroVector;
1843 CornerPositions[CornerIndex] = InVertices[InIndices[WedgeOffset + CornerIndex]];
1844 RelevantFacesForCorner[CornerIndex].Reset();
1845 }
1846
1847 // Don't process degenerate triangles.
1848 if (PointsEqual(CornerPositions[0], CornerPositions[1], ComparisonThreshold)
1849 || PointsEqual(CornerPositions[0], CornerPositions[2], ComparisonThreshold)
1850 || PointsEqual(CornerPositions[1], CornerPositions[2], ComparisonThreshold))
1851 {
1852 continue;
1853 }
1854
1855 // No need to process triangles if tangents already exist.
1856 bool bCornerHasNormal[3] = { 0 };
1857 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
1858 {
1859 bCornerHasNormal[CornerIndex] = !OutTangentZ[WedgeOffset + CornerIndex].IsZero();
1860 }
1861 if (bCornerHasNormal[0] && bCornerHasNormal[1] && bCornerHasNormal[2])
1862 {
1863 continue;
1864 }
1865
1866 // Start building a list of faces adjacent to this face.
1867// AdjacentFaces.Reset();
1868 TSet<int32> AdjacentFaces;
1869 for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
1870 {
1871 int32 ThisCornerIndex = WedgeOffset + CornerIndex;
1872 const TArray<int32>& DupVerts = OverlappingCorners.FindIfOverlapping(ThisCornerIndex);
1873 if (DupVerts.Num() == 0)
1874 {
1875// AdjacentFaces.AddUnique(ThisCornerIndex / 3); // I am a "dup" of myself
1876 AdjacentFaces.Add(ThisCornerIndex / 3); // I am a "dup" of myself
1877 }
1878 for (int32 k = 0; k < DupVerts.Num(); k++)
1879 {
1880 AdjacentFaces.Add(DupVerts[k] / 3);
1881 }
1882 }
1883
1884 // We need to sort these here because the criteria for point equality is
1885 // exact, so we must ensure the exact same order for all dups.
1886// AdjacentFaces.Sort();
1887
1888 // Process adjacent faces
1889// for (int32 AdjacentFaceIndex = 0; AdjacentFaceIndex < AdjacentFaces.Num(); AdjacentFaceIndex++)
1890 for (int32 OtherFaceIndex : AdjacentFaces )
1891 {
1892// int32 OtherFaceIndex = AdjacentFaces[AdjacentFaceIndex];
1893 for (int32 OurCornerIndex = 0; OurCornerIndex < 3; ++OurCornerIndex)
1894 {
1895 if (bCornerHasNormal[OurCornerIndex])
1896 continue;
1897
1898 FFanFace NewFanFace;
1899 int32 CommonIndexCount = 0;
1900
1901 // Check for vertices in common.
1902 if (FaceIndex == OtherFaceIndex)
1903 {
1904 CommonIndexCount = 3;
1905 NewFanFace.LinkedVertexIndex = OurCornerIndex;
1906 }
1907 else
1908 {
1909 // Check matching vertices against main vertex .
1910 for (int32 OtherCornerIndex = 0; OtherCornerIndex < 3; OtherCornerIndex++)
1911 {
1912 if (PointsEqual(
1913 CornerPositions[OurCornerIndex],
1914 InVertices[InIndices[OtherFaceIndex * 3 + OtherCornerIndex]],
1915 ComparisonThreshold
1916 ))
1917 {
1918 CommonIndexCount++;
1919 NewFanFace.LinkedVertexIndex = OtherCornerIndex;
1920 }
1921 }
1922 }
1923
1924 // Add if connected by at least one point. Smoothing matches are considered later.
1925 if (CommonIndexCount > 0)
1926 {
1927 NewFanFace.FaceIndex = OtherFaceIndex;
1928 NewFanFace.bFilled = (OtherFaceIndex == FaceIndex); // Starter face for smoothing floodfill.
1929 NewFanFace.bBlendTangents = NewFanFace.bFilled;
1930 NewFanFace.bBlendNormals = NewFanFace.bFilled;
1931 RelevantFacesForCorner[OurCornerIndex].Add(NewFanFace);
1932 }
1933 }
1934 }
1935
1936 // Find true relevance of faces for a vertex normal by traversing
1937 // smoothing-group-compatible connected triangle fans around common vertices.
1938 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
1939 {
1940 if (bCornerHasNormal[CornerIndex])
1941 continue;
1942
1943 int32 NewConnections;
1944 do
1945 {
1946 NewConnections = 0;
1947 for (int32 OtherFaceIdx = 0; OtherFaceIdx < RelevantFacesForCorner[CornerIndex].Num(); OtherFaceIdx++)
1948 {
1949 FFanFace& OtherFace = RelevantFacesForCorner[CornerIndex][OtherFaceIdx];
1950 // The vertex' own face is initially the only face with bFilled == true.
1951 if (OtherFace.bFilled)
1952 {
1953 for (int32 NextFaceIndex = 0; NextFaceIndex < RelevantFacesForCorner[CornerIndex].Num(); NextFaceIndex++)
1954 {
1955 FFanFace& NextFace = RelevantFacesForCorner[CornerIndex][NextFaceIndex];
1956 if (!NextFace.bFilled) // && !NextFace.bBlendTangents)
1957 {
1958 if ((NextFaceIndex != OtherFaceIdx)
1959 && (SmoothingGroupIndices[NextFace.FaceIndex] & SmoothingGroupIndices[OtherFace.FaceIndex]))
1960 {
1961 int32 CommonVertices = 0;
1962 int32 CommonNormalVertices = 0;
1963 for (int32 OtherCornerIndex = 0; OtherCornerIndex < 3; ++OtherCornerIndex)
1964 {
1965 for (int32 NextCornerIndex = 0; NextCornerIndex < 3; ++NextCornerIndex)
1966 {
1967 int32 NextVertexIndex = InIndices[NextFace.FaceIndex * 3 + NextCornerIndex];
1968 int32 OtherVertexIndex = InIndices[OtherFace.FaceIndex * 3 + OtherCornerIndex];
1969 if (PointsEqual(
1970 InVertices[NextVertexIndex],
1971 InVertices[OtherVertexIndex],
1972 ComparisonThreshold))
1973 {
1974 CommonVertices++;
1975 if (bBlendOverlappingNormals
1976 || NextVertexIndex == OtherVertexIndex)
1977 {
1978 CommonNormalVertices++;
1979 }
1980 }
1981 }
1982 }
1983 // Flood fill faces with more than one common vertices which must be touching edges.
1984 if (CommonVertices > 1)
1985 {
1986 NextFace.bFilled = true;
1987 NextFace.bBlendNormals = (CommonNormalVertices > 1);
1988 NewConnections++;
1989 }
1990 }
1991 }
1992 }
1993 }
1994 }
1995 }
1996 while (NewConnections > 0);
1997 }
1998
1999
2000 // Vertex normal construction.
2001 for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
2002 {
2003 if (bCornerHasNormal[CornerIndex])
2004 {
2005 CornerNormal[CornerIndex] = OutTangentZ[WedgeOffset + CornerIndex];
2006 }
2007 else
2008 {
2009 for (int32 RelevantFaceIdx = 0; RelevantFaceIdx < RelevantFacesForCorner[CornerIndex].Num(); RelevantFaceIdx++)
2010 {
2011 FFanFace const& RelevantFace = RelevantFacesForCorner[CornerIndex][RelevantFaceIdx];
2012 if (RelevantFace.bFilled)
2013 {
2014 int32 OtherFaceIndex = RelevantFace.FaceIndex;
2015 if (RelevantFace.bBlendNormals)
2016 {
2017 CornerNormal[CornerIndex] += TriangleTangentZ[OtherFaceIndex];
2018 }
2019 }
2020 }
2021 if (!OutTangentZ[WedgeOffset + CornerIndex].IsZero())
2022 {
2023 CornerNormal[CornerIndex] = OutTangentZ[WedgeOffset + CornerIndex];
2024 }
2025 }
2026 }
2027
2028 // Normalization.
2029 for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
2030 {
2031 CornerNormal[CornerIndex].Normalize();
2032 }
2033
2034 // Copy back to the mesh.
2035 for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
2036 {
2037 OutTangentZ[WedgeOffset + CornerIndex] = CornerNormal[CornerIndex];
2038 }
2039 }
2040
2041 check(OutTangentZ.Num() == NumWedges);
2042}
2043
2044static void ComputeTangents_MikkTSpace(
2045 const TArray<FVector>& InVertices,
2046 const TArray<uint32>& InIndices,
2047 const TArray<FVector2D>& InUVs,
2048 const TArray<uint32>& SmoothingGroupIndices,
2049 const FOverlappingCorners& OverlappingCorners,
2050 TArray<FVector>& OutTangentX,
2051 TArray<FVector>& OutTangentY,
2052 TArray<FVector>& OutTangentZ,
2053 const uint32 TangentOptions
2054 )
2055{
2056 ComputeNormals( InVertices, InIndices, InUVs, SmoothingGroupIndices, OverlappingCorners, OutTangentZ, TangentOptions );
2057
2058 bool bIgnoreDegenerateTriangles = (TangentOptions & ETangentOptions::IgnoreDegenerateTriangles) != 0;
2059
2060 int32 NumWedges = InIndices.Num();
2061
2062 bool bWedgeTSpace = false;
2063
2064 if (OutTangentX.Num() > 0 && OutTangentY.Num() > 0)
2065 {
2066 bWedgeTSpace = true;
2067 for (int32 WedgeIdx = 0; WedgeIdx < OutTangentX.Num()
2068 && WedgeIdx < OutTangentY.Num(); ++WedgeIdx)
2069 {
2070 bWedgeTSpace = bWedgeTSpace && (!OutTangentX[WedgeIdx].IsNearlyZero()) && (!OutTangentY[WedgeIdx].IsNearlyZero());
2071 }
2072 }
2073
2074 if (OutTangentX.Num() != NumWedges)
2075 {
2076 OutTangentX.Empty(NumWedges);
2077 OutTangentX.AddZeroed(NumWedges);
2078 }
2079 if (OutTangentY.Num() != NumWedges)
2080 {
2081 OutTangentY.Empty(NumWedges);
2082 OutTangentY.AddZeroed(NumWedges);
2083 }
2084
2085 if (!bWedgeTSpace)
2086 {
2087 MikkTSpace_Mesh MikkTSpaceMesh( InVertices, InIndices, InUVs, OutTangentX, OutTangentY, OutTangentZ );
2088
2089 // we can use mikktspace to calculate the tangents
2090 SMikkTSpaceInterface MikkTInterface;
2091 MikkTInterface.m_getNormal = MikkGetNormal;
2092 MikkTInterface.m_getNumFaces = MikkGetNumFaces;
2093 MikkTInterface.m_getNumVerticesOfFace = MikkGetNumVertsOfFace;
2094 MikkTInterface.m_getPosition = MikkGetPosition;
2095 MikkTInterface.m_getTexCoord = MikkGetTexCoord;
2096 MikkTInterface.m_setTSpaceBasic = MikkSetTSpaceBasic;
2097 MikkTInterface.m_setTSpace = nullptr;
2098
2099 SMikkTSpaceContext MikkTContext;
2100 MikkTContext.m_pInterface = &MikkTInterface;
2101 MikkTContext.m_pUserData = (void*)(&MikkTSpaceMesh);
2102 MikkTContext.m_bIgnoreDegenerates = bIgnoreDegenerateTriangles;
2103 genTangSpaceDefault(&MikkTContext);
2104 }
2105
2106 check(OutTangentX.Num() == NumWedges);
2107 check(OutTangentY.Num() == NumWedges);
2108 check(OutTangentZ.Num() == NumWedges);
2109}
2110
2111static void ComputeTangents_MikkTSpace(
2112 FRawMesh& RawMesh,
2113 const FOverlappingCorners& OverlappingCorners,
2114 uint32 TangentOptions
2115 )
2116{
2117 ComputeTangents_MikkTSpace(RawMesh.VertexPositions, RawMesh.WedgeIndices, RawMesh.WedgeTexCoords[0], RawMesh.FaceSmoothingMasks, OverlappingCorners, RawMesh.WedgeTangentX, RawMesh.WedgeTangentY, RawMesh.WedgeTangentZ, TangentOptions);
2118}
2119
2120static void BuildDepthOnlyIndexBuffer(
2121 TArray<uint32>& OutDepthIndices,
2122 const TArray<FStaticMeshBuildVertex>& InVertices,
2123 const TArray<uint32>& InIndices,
2124 const TArrayView<FStaticMeshSection>& InSections
2125 )
2126{
2127 int32 NumVertices = InVertices.Num();
2128 if (InIndices.Num() <= 0 || NumVertices <= 0)
2129 {
2130 OutDepthIndices.Empty();
2131 return;
2132 }
2133
2134 // Create a mapping of index -> first overlapping index to accelerate the construction of the shadow index buffer.
2135 TArray<FIndexAndZ> VertIndexAndZ;
2136 VertIndexAndZ.Empty(NumVertices);
2137 for (int32 VertIndex = 0; VertIndex < NumVertices; VertIndex++)
2138 {
2139 new(VertIndexAndZ)FIndexAndZ(VertIndex, InVertices[VertIndex].Position);
2140 }
2141 VertIndexAndZ.Sort(FCompareIndexAndZ());
2142
2143 // Setup the index map. 0xFFFFFFFF == not set.
2144 TArray<uint32> IndexMap;
2145 IndexMap.AddUninitialized(NumVertices);
2146 FMemory::Memset(IndexMap.GetData(), 0xFF, NumVertices * sizeof(uint32));
2147
2148 // Search for duplicates, quickly!
2149 for (int32 i = 0; i < VertIndexAndZ.Num(); i++)
2150 {
2151 uint32 SrcIndex = VertIndexAndZ[i].Index;
2152 float Z = VertIndexAndZ[i].Z;
2153 IndexMap[SrcIndex] = FMath::Min(IndexMap[SrcIndex], SrcIndex);
2154
2155 // Search forward since we add pairs both ways.
2156 for (int32 j = i + 1; j < VertIndexAndZ.Num(); j++)
2157 {
2158 if (FMath::Abs(VertIndexAndZ[j].Z - Z) > THRESH_POINTS_ARE_SAME * 4.01f)
2159 break; // can't be any more dups
2160
2161 uint32 OtherIndex = VertIndexAndZ[j].Index;
2162 if (PointsEqual(InVertices[SrcIndex].Position, InVertices[OtherIndex].Position,/*bUseEpsilonCompare=*/ false))
2163 {
2164 IndexMap[SrcIndex] = FMath::Min(IndexMap[SrcIndex], OtherIndex);
2165 IndexMap[OtherIndex] = FMath::Min(IndexMap[OtherIndex], SrcIndex);
2166 }
2167 }
2168 }
2169
2170 // Build the depth-only index buffer by remapping all indices to the first overlapping
2171 // vertex in the vertex buffer.
2172 OutDepthIndices.Empty();
2173 for (int32 SectionIndex = 0; SectionIndex < InSections.Num(); ++SectionIndex)
2174 {
2175 const FStaticMeshSection& Section = InSections[SectionIndex];
2176 int32 FirstIndex = Section.FirstIndex;
2177 int32 LastIndex = FirstIndex + Section.NumTriangles * 3;
2178 for (int32 SrcIndex = FirstIndex; SrcIndex < LastIndex; ++SrcIndex)
2179 {
2180 uint32 VertIndex = InIndices[SrcIndex];
2181 OutDepthIndices.Add(IndexMap[VertIndex]);
2182 }
2183 }
2184}
2185
2186static float GetComparisonThreshold(FMeshBuildSettings const& BuildSettings)
2187{
2188 return BuildSettings.bRemoveDegenerates ? THRESH_POINTS_ARE_SAME : 0.0f;
2189}
2190
2191/*------------------------------------------------------------------------------
2192Static mesh building.
2193------------------------------------------------------------------------------*/
2194
2195static void BuildStaticMeshVertex(const FRawMesh& RawMesh, const FMatrix& ScaleMatrix, const FVector& Position, int32 WedgeIndex, FStaticMeshBuildVertex& Vertex)
2196{
2197 Vertex.Position = Position;
2198
2199 Vertex.TangentX = ScaleMatrix.TransformVector(RawMesh.WedgeTangentX[WedgeIndex]).GetSafeNormal();
2200 Vertex.TangentY = ScaleMatrix.TransformVector(RawMesh.WedgeTangentY[WedgeIndex]).GetSafeNormal();
2201 Vertex.TangentZ = ScaleMatrix.TransformVector(RawMesh.WedgeTangentZ[WedgeIndex]).GetSafeNormal();
2202
2203 if (RawMesh.WedgeColors.IsValidIndex(WedgeIndex))
2204 {
2205 Vertex.Color = RawMesh.WedgeColors[WedgeIndex];
2206 }
2207 else
2208 {
2209 Vertex.Color = FColor::White;
2210 }
2211
2212 static const int32 NumTexCoords = FMath::Min<int32>(MAX_MESH_TEXTURE_COORDS, MAX_STATIC_TEXCOORDS);
2213 for (int32 i = 0; i < NumTexCoords; ++i)
2214 {
2215 if (RawMesh.WedgeTexCoords[i].IsValidIndex(WedgeIndex))
2216 {
2217 Vertex.UVs[i] = RawMesh.WedgeTexCoords[i][WedgeIndex];
2218 }
2219 else
2220 {
2221 Vertex.UVs[i] = FVector2D(0.0f, 0.0f);
2222 }
2223 }
2224}
2225
2226static bool AreVerticesEqual(
2227 FStaticMeshBuildVertex const& A,
2228 FStaticMeshBuildVertex const& B,
2229 float ComparisonThreshold
2230 )
2231{
2232 if (!PointsEqual(A.Position, B.Position, ComparisonThreshold)
2233 || !NormalsEqual(A.TangentX, B.TangentX)
2234 || !NormalsEqual(A.TangentY, B.TangentY)
2235 || !NormalsEqual(A.TangentZ, B.TangentZ)
2236 || A.Color != B.Color)
2237 {
2238 return false;
2239 }
2240
2241 // UVs
2242 for (int32 UVIndex = 0; UVIndex < MAX_STATIC_TEXCOORDS; UVIndex++)
2243 {
2244 if (!UVsEqual(A.UVs[UVIndex], B.UVs[UVIndex]))
2245 {
2246 return false;
2247 }
2248 }
2249
2250 return true;
2251}
2252
2253void FMeshUtilities::BuildStaticMeshVertexAndIndexBuffers(
2254 TArray<FStaticMeshBuildVertex>& OutVertices,
2255 TArray<TArray<uint32> >& OutPerSectionIndices,
2256 TArray<int32>& OutWedgeMap,
2257 const FRawMesh& RawMesh,
2258 const FOverlappingCorners& OverlappingCorners,
2259 const TMap<uint32, uint32>& MaterialToSectionMapping,
2260 float ComparisonThreshold,
2261 FVector BuildScale,
2262 int32 ImportVersion
2263 )
2264{
2265 TMap<int32, int32> FinalVerts;
2266 int32 NumFaces = RawMesh.WedgeIndices.Num() / 3;
2267 OutWedgeMap.Reset(RawMesh.WedgeIndices.Num());
2268 FMatrix ScaleMatrix(FScaleMatrix(BuildScale).Inverse().GetTransposed());
2269
2270 // Estimate how many vertices there will be to reduce number of re-allocations required
2271 OutVertices.Reserve((int32)(NumFaces * 1.2) + 16);
2272
2273 // Work with vertex in OutVertices array directly for improved performance
2274 OutVertices.AddUninitialized(1);
2275 FStaticMeshBuildVertex *ThisVertex = &OutVertices.Last();
2276
2277 // Process each face, build vertex buffer and per-section index buffers.
2278 for (int32 FaceIndex = 0; FaceIndex < NumFaces; FaceIndex++)
2279 {
2280 int32 VertexIndices[3];
2281 FVector CornerPositions[3];
2282
2283 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
2284 {
2285 CornerPositions[CornerIndex] = GetPositionForWedge(RawMesh, FaceIndex * 3 + CornerIndex);
2286 }
2287
2288 // Don't process degenerate triangles.
2289 if (PointsEqual(CornerPositions[0], CornerPositions[1], ComparisonThreshold)
2290 || PointsEqual(CornerPositions[0], CornerPositions[2], ComparisonThreshold)
2291 || PointsEqual(CornerPositions[1], CornerPositions[2], ComparisonThreshold))
2292 {
2293 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
2294 {
2295 OutWedgeMap.Add(INDEX_NONE);
2296 }
2297 continue;
2298 }
2299
2300 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
2301 {
2302 int32 WedgeIndex = FaceIndex * 3 + CornerIndex;
2303 BuildStaticMeshVertex(RawMesh, ScaleMatrix, CornerPositions[CornerIndex] * BuildScale, WedgeIndex, *ThisVertex);
2304
2305 const TArray<int32>& DupVerts = OverlappingCorners.FindIfOverlapping(WedgeIndex);
2306
2307 int32 Index = INDEX_NONE;
2308 for (int32 k = 0; k < DupVerts.Num(); k++)
2309 {
2310 if (DupVerts[k] >= WedgeIndex)
2311 {
2312 // the verts beyond me haven't been placed yet, so these duplicates are not relevant
2313 break;
2314 }
2315
2316 int32 *Location = FinalVerts.Find(DupVerts[k]);
2317 if (Location != NULL
2318 && AreVerticesEqual(*ThisVertex, OutVertices[*Location], ComparisonThreshold))
2319 {
2320 Index = *Location;
2321 break;
2322 }
2323 }
2324 if (Index == INDEX_NONE)
2325 {
2326 // Commit working vertex
2327 Index = OutVertices.Num() - 1;
2328 FinalVerts.Add(WedgeIndex, Index);
2329
2330 // Setup next working vertex
2331 OutVertices.AddUninitialized(1);
2332 ThisVertex = &OutVertices.Last();
2333 }
2334 VertexIndices[CornerIndex] = Index;
2335 }
2336
2337 // Reject degenerate triangles.
2338 if (VertexIndices[0] == VertexIndices[1]
2339 || VertexIndices[1] == VertexIndices[2]
2340 || VertexIndices[0] == VertexIndices[2])
2341 {
2342 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
2343 {
2344 OutWedgeMap.Add(INDEX_NONE);
2345 }
2346 continue;
2347 }
2348
2349 // Put the indices in the material index buffer.
2350 uint32 SectionIndex = 0;
2351 if (ImportVersion < RemoveStaticMeshSkinxxWorkflow)
2352 {
2353 SectionIndex = FMath::Clamp(RawMesh.FaceMaterialIndices[FaceIndex], 0, OutPerSectionIndices.Num() - 1);
2354 }
2355 else
2356 {
2357 SectionIndex = MaterialToSectionMapping.FindChecked(RawMesh.FaceMaterialIndices[FaceIndex]);
2358 }
2359 TArray<uint32>& SectionIndices = OutPerSectionIndices[SectionIndex];
2360 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
2361 {
2362 SectionIndices.Add(VertexIndices[CornerIndex]);
2363 OutWedgeMap.Add(VertexIndices[CornerIndex]);
2364 }
2365 }
2366
2367 // Remove working vertex
2368 OutVertices.Pop(false);
2369}
2370
2371void FMeshUtilities::CacheOptimizeVertexAndIndexBuffer(
2372 TArray<FStaticMeshBuildVertex>& Vertices,
2373 TArray<TArray<uint32> >& PerSectionIndices,
2374 TArray<int32>& WedgeMap
2375 )
2376{
2377 // Copy the vertices since we will be reordering them
2378 TArray<FStaticMeshBuildVertex> OriginalVertices = Vertices;
2379
2380 // Initialize a cache that stores which indices have been assigned
2381 TArray<int32> IndexCache;
2382 IndexCache.AddUninitialized(Vertices.Num());
2383 FMemory::Memset(IndexCache.GetData(), INDEX_NONE, IndexCache.Num() * IndexCache.GetTypeSize());
2384 int32 NextAvailableIndex = 0;
2385
2386 // Iterate through the section index buffers,
2387 // Optimizing index order for the post transform cache (minimizes the number of vertices transformed),
2388 // And vertex order for the pre transform cache (minimizes the amount of vertex data fetched by the GPU).
2389 for (int32 SectionIndex = 0; SectionIndex < PerSectionIndices.Num(); SectionIndex++)
2390 {
2391 TArray<uint32>& Indices = PerSectionIndices[SectionIndex];
2392
2393 if (Indices.Num())
2394 {
2395 // Optimize the index buffer for the post transform cache with.
2396 CacheOptimizeIndexBuffer(Indices);
2397
2398 // Copy the index buffer since we will be reordering it
2399 TArray<uint32> OriginalIndices = Indices;
2400
2401 // Go through the indices and assign them new values that are coherent where possible
2402 for (int32 Index = 0; Index < Indices.Num(); Index++)
2403 {
2404 const int32 CachedIndex = IndexCache[OriginalIndices[Index]];
2405
2406 if (CachedIndex == INDEX_NONE)
2407 {
2408 // No new index has been allocated for this existing index, assign a new one
2409 Indices[Index] = NextAvailableIndex;
2410 // Mark what this index has been assigned to
2411 IndexCache[OriginalIndices[Index]] = NextAvailableIndex;
2412 NextAvailableIndex++;
2413 }
2414 else
2415 {
2416 // Reuse an existing index assignment
2417 Indices[Index] = CachedIndex;
2418 }
2419 // Reorder the vertices based on the new index assignment
2420 Vertices[Indices[Index]] = OriginalVertices[OriginalIndices[Index]];
2421 }
2422 }
2423 }
2424
2425 for (int32 i = 0; i < WedgeMap.Num(); i++)
2426 {
2427 int32 MappedIndex = WedgeMap[i];
2428 if (MappedIndex != INDEX_NONE)
2429 {
2430 WedgeMap[i] = IndexCache[MappedIndex];
2431 }
2432 }
2433}
2434
2435struct FLayoutUVRawMeshView final : FLayoutUV::IMeshView
2436{
2437 FRawMesh& RawMesh;
2438 const uint32 SrcChannel;
2439 const uint32 DstChannel;
2440 const bool bNormalsValid;
2441
2442 FLayoutUVRawMeshView(FRawMesh& InRawMesh, uint32 InSrcChannel, uint32 InDstChannel)
2443 : RawMesh(InRawMesh)
2444 , SrcChannel(InSrcChannel)
2445 , DstChannel(InDstChannel)
2446 , bNormalsValid(InRawMesh.WedgeTangentZ.Num() == InRawMesh.WedgeTexCoords[InSrcChannel].Num())
2447 {}
2448
2449 uint32 GetNumIndices() const override { return RawMesh.WedgeIndices.Num(); }
2450 FVector GetPosition(uint32 Index) const override { return RawMesh.GetWedgePosition(Index); }
2451 FVector GetNormal(uint32 Index) const override { return bNormalsValid ? RawMesh.WedgeTangentZ[Index] : FVector::ZeroVector; }
2452 FVector2D GetInputTexcoord(uint32 Index) const override { return RawMesh.WedgeTexCoords[SrcChannel][Index]; }
2453
2454 void InitOutputTexcoords(uint32 Num) override { RawMesh.WedgeTexCoords[DstChannel].SetNumUninitialized( Num ); }
2455 void SetOutputTexcoord(uint32 Index, const FVector2D& Value) override { RawMesh.WedgeTexCoords[DstChannel][Index] = Value; }
2456};
2457
2458class FStaticMeshUtilityBuilder
2459{
2460public:
2461 FStaticMeshUtilityBuilder(UStaticMesh* InStaticMesh) : Stage(EStage::Uninit), NumValidLODs(0), StaticMesh(InStaticMesh) {}
2462
2463 bool GatherSourceMeshesPerLOD(IMeshReduction* MeshReduction)
2464 {
2465 check(Stage == EStage::Uninit);
2466 check(StaticMesh != nullptr);
2467 TArray<FStaticMeshSourceModel>& SourceModels = StaticMesh->GetSourceModels();
2468 ELightmapUVVersion LightmapUVVersion = (ELightmapUVVersion)StaticMesh->LightmapUVVersion;
2469
2470 FMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<FMeshUtilities>("MeshUtilities");
2471
2472 // Gather source meshes for each LOD.
2473 for (int32 LODIndex = 0; LODIndex < SourceModels.Num(); ++LODIndex)
2474 {
2475 FStaticMeshSourceModel& SrcModel = SourceModels[LODIndex];
2476 FRawMesh& RawMesh = *new FRawMesh;
2477 LODMeshes.Add(&RawMesh);
2478 FOverlappingCorners& OverlappingCorners = *new FOverlappingCorners;
2479 LODOverlappingCorners.Add(&OverlappingCorners);
2480
2481 if (!SrcModel.IsRawMeshEmpty())
2482 {
2483 SrcModel.LoadRawMesh(RawMesh);
2484 // Make sure the raw mesh is not irreparably malformed.
2485 if (!RawMesh.IsValidOrFixable())
2486 {
2487 UE_LOG(LogMeshUtilities, Error, TEXT("Raw mesh is corrupt for LOD%d."), LODIndex);
2488 return false;
2489 }
2490 LODBuildSettings[LODIndex] = SrcModel.BuildSettings;
2491
2492 float ComparisonThreshold = GetComparisonThreshold(LODBuildSettings[LODIndex]);
2493 int32 NumWedges = RawMesh.WedgeIndices.Num();
2494
2495 // Find overlapping corners to accelerate adjacency.
2496 MeshUtilities.FindOverlappingCorners(OverlappingCorners, RawMesh, ComparisonThreshold);
2497
2498 // Figure out if we should recompute normals and tangents.
2499 bool bRecomputeNormals = SrcModel.BuildSettings.bRecomputeNormals || RawMesh.WedgeTangentZ.Num() != NumWedges;
2500 bool bRecomputeTangents = SrcModel.BuildSettings.bRecomputeTangents || RawMesh.WedgeTangentX.Num() != NumWedges || RawMesh.WedgeTangentY.Num() != NumWedges;
2501
2502 // Dump normals and tangents if we are recomputing them.
2503 if (bRecomputeTangents)
2504 {
2505 RawMesh.WedgeTangentX.Empty(NumWedges);
2506 RawMesh.WedgeTangentX.AddZeroed(NumWedges);
2507 RawMesh.WedgeTangentY.Empty(NumWedges);
2508 RawMesh.WedgeTangentY.AddZeroed(NumWedges);
2509 }
2510 if (bRecomputeNormals)
2511 {
2512 RawMesh.WedgeTangentZ.Empty(NumWedges);
2513 RawMesh.WedgeTangentZ.AddZeroed(NumWedges);
2514 }
2515
2516 // Compute any missing tangents.
2517 {
2518 // Static meshes always blend normals of overlapping corners.
2519 uint32 TangentOptions = ETangentOptions::BlendOverlappingNormals;
2520 if (SrcModel.BuildSettings.bRemoveDegenerates)
2521 {
2522 // If removing degenerate triangles, ignore them when computing tangents.
2523 TangentOptions |= ETangentOptions::IgnoreDegenerateTriangles;
2524 }
2525
2526 //MikkTSpace should be use only when the user want to recompute the normals or tangents otherwise should always fallback on builtin
2527 if (SrcModel.BuildSettings.bUseMikkTSpace && (SrcModel.BuildSettings.bRecomputeNormals || SrcModel.BuildSettings.bRecomputeTangents))
2528 {
2529 ComputeTangents_MikkTSpace(RawMesh, OverlappingCorners, TangentOptions);
2530 }
2531 else
2532 {
2533 ComputeTangents(RawMesh, OverlappingCorners, TangentOptions);
2534 }
2535 }
2536
2537 // At this point the mesh will have valid tangents.
2538 check(RawMesh.WedgeTangentX.Num() == NumWedges);
2539 check(RawMesh.WedgeTangentY.Num() == NumWedges);
2540 check(RawMesh.WedgeTangentZ.Num() == NumWedges);
2541
2542 // Generate lightmap UVs
2543 if (SrcModel.BuildSettings.bGenerateLightmapUVs)
2544 {
2545 if (RawMesh.WedgeTexCoords[SrcModel.BuildSettings.SrcLightmapIndex].Num() == 0)
2546 {
2547 SrcModel.BuildSettings.SrcLightmapIndex = 0;
2548 }
2549
2550 FLayoutUVRawMeshView RawMeshView(RawMesh, SrcModel.BuildSettings.SrcLightmapIndex, SrcModel.BuildSettings.DstLightmapIndex);
2551 FLayoutUV Packer(RawMeshView);
2552 Packer.SetVersion(LightmapUVVersion);
2553
2554 Packer.FindCharts(OverlappingCorners);
2555
2556 int32 EffectiveMinLightmapResolution = SrcModel.BuildSettings.MinLightmapResolution;
2557 if (LightmapUVVersion >= ELightmapUVVersion::ConsiderLightmapPadding)
2558 {
2559 if (GLightmassDebugOptions.bPadMappings)
2560 {
2561 EffectiveMinLightmapResolution -= 2;
2562 }
2563 }
2564
2565 bool bPackSuccess = Packer.FindBestPacking(EffectiveMinLightmapResolution);
2566 if (bPackSuccess)
2567 {
2568 Packer.CommitPackedUVs();
2569 }
2570 }
2571 HasRawMesh[LODIndex] = true;
2572 }
2573 else if (LODIndex > 0 && MeshReduction)
2574 {
2575 // If a raw mesh is not explicitly provided, use the raw mesh of the
2576 // next highest LOD.
2577 int32 BaseRawMeshIndex = LODIndex - 1;
2578 RawMesh = LODMeshes[BaseRawMeshIndex];
2579 OverlappingCorners = LODOverlappingCorners[BaseRawMeshIndex];
2580 LODBuildSettings[LODIndex] = LODBuildSettings[BaseRawMeshIndex];
2581 HasRawMesh[LODIndex] = false;
2582 //Make sure the SectionInfoMap is taken from the Base RawMesh
2583 int32 SectionNumber = StaticMesh->GetOriginalSectionInfoMap().GetSectionNumber(BaseRawMeshIndex);
2584 for (int32 SectionIndex = 0; SectionIndex < SectionNumber; ++SectionIndex)
2585 {
2586 FMeshSectionInfo Info = StaticMesh->GetOriginalSectionInfoMap().Get(BaseRawMeshIndex, SectionIndex);
2587 StaticMesh->GetSectionInfoMap().Set(LODIndex, SectionIndex, Info);
2588 StaticMesh->GetOriginalSectionInfoMap().Set(LODIndex, SectionIndex, Info);
2589 }
2590 }
2591 }
2592 check(LODMeshes.Num() == SourceModels.Num());
2593 check(LODOverlappingCorners.Num() == SourceModels.Num());
2594
2595 // Bail if there is no raw mesh data from which to build a renderable mesh.
2596 if (LODMeshes.Num() == 0)
2597 {
2598 UE_LOG(LogMeshUtilities, Error, TEXT("Raw Mesh data contains no mesh data to build a mesh that can be rendered."));
2599 return false;
2600 }
2601 else if (LODMeshes[0].WedgeIndices.Num() == 0)
2602 {
2603 UE_LOG(LogMeshUtilities, Error, TEXT("Raw Mesh data contains no wedge index data to build a mesh that can be rendered."));
2604 return false;
2605 }
2606
2607 Stage = EStage::Gathered;
2608 return true;
2609 }
2610
2611 bool ReduceLODs(const FStaticMeshLODGroup& LODGroup, IMeshReduction* MeshReduction, TArray<bool>& OutWasReduced)
2612 {
2613 check(Stage == EStage::Gathered);
2614 check(StaticMesh != nullptr);
2615 TArray<FStaticMeshSourceModel>& SourceModels = StaticMesh->GetSourceModels();
2616 if (SourceModels.Num() == 0)
2617 {
2618 UE_LOG(LogMeshUtilities, Error, TEXT("Mesh contains zero source models."));
2619 return false;
2620 }
2621
2622 FMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<FMeshUtilities>("MeshUtilities");
2623
2624 // Reduce each LOD mesh according to its reduction settings.
2625 for (int32 LODIndex = 0; LODIndex < SourceModels.Num(); ++LODIndex)
2626 {
2627 const FStaticMeshSourceModel& SrcModel = SourceModels[LODIndex];
2628 FMeshReductionSettings ReductionSettings = LODGroup.GetSettings(SrcModel.ReductionSettings, LODIndex);
2629 LODMaxDeviation[NumValidLODs] = 0.0f;
2630 if (LODIndex != NumValidLODs)
2631 {
2632 LODBuildSettings[NumValidLODs] = LODBuildSettings[LODIndex];
2633 LODOverlappingCorners[NumValidLODs] = LODOverlappingCorners[LODIndex];
2634 }
2635
2636 if (MeshReduction && (ReductionSettings.PercentTriangles < 1.0f || ReductionSettings.MaxDeviation > 0.0f))
2637 {
2638 FRawMesh& InMesh = LODMeshes[ReductionSettings.BaseLODModel];
2639 FRawMesh& DestMesh = LODMeshes[NumValidLODs];
2640 FOverlappingCorners& InOverlappingCorners = LODOverlappingCorners[ReductionSettings.BaseLODModel];
2641 FOverlappingCorners& DestOverlappingCorners = LODOverlappingCorners[NumValidLODs];
2642
2643 FMeshDescription SrcMeshdescription;
2644 FStaticMeshAttributes(SrcMeshdescription).Register();
2645
2646 FMeshDescription DestMeshdescription;
2647 FStaticMeshAttributes(DestMeshdescription).Register();
2648
2649 TMap<int32, FName> FromMaterialMap;
2650 FStaticMeshOperations::ConvertFromRawMesh(InMesh, SrcMeshdescription, FromMaterialMap);
2651 MeshReduction->ReduceMeshDescription(DestMeshdescription, LODMaxDeviation[NumValidLODs], SrcMeshdescription, InOverlappingCorners, ReductionSettings);
2652 TMap<FName, int32> ToMaterialMap;
2653 FStaticMeshOperations::ConvertToRawMesh(DestMeshdescription, DestMesh, ToMaterialMap);
2654
2655 if (DestMesh.WedgeIndices.Num() > 0 && !DestMesh.IsValid())
2656 {
2657 UE_LOG(LogMeshUtilities, Error, TEXT("Mesh reduction produced a corrupt mesh for LOD%d"), LODIndex);
2658 return false;
2659 }
2660 OutWasReduced[LODIndex] = true;
2661
2662 // Recompute adjacency information.
2663 float ComparisonThreshold = GetComparisonThreshold(LODBuildSettings[NumValidLODs]);
2664 MeshUtilities.FindOverlappingCorners(DestOverlappingCorners, DestMesh, ComparisonThreshold);
2665
2666 //Make sure the static mesh SectionInfoMap is up to date with the new reduce LOD
2667 //We have to remap the material index with the ReductionSettings.BaseLODModel sectionInfoMap
2668 if (StaticMesh != nullptr)
2669 {
2670 if (DestMesh.IsValid())
2671 {
2672 //Set the new SectionInfoMap for this reduced LOD base on the ReductionSettings.BaseLODModel SectionInfoMap
2673 const FMeshSectionInfoMap& BaseLODModelSectionInfoMap = StaticMesh->GetSectionInfoMap();
2674 TArray<int32> UniqueMaterialIndex;
2675 //Find all unique Material in used order
2676 int32 NumFaces = DestMesh.FaceMaterialIndices.Num();
2677 for (int32 FaceIndex = 0; FaceIndex < NumFaces; ++FaceIndex)
2678 {
2679 int32 MaterialIndex = DestMesh.FaceMaterialIndices[FaceIndex];
2680 UniqueMaterialIndex.AddUnique(MaterialIndex);
2681 }
2682 //All used material represent a different section
2683 for (int32 SectionIndex = 0; SectionIndex < UniqueMaterialIndex.Num(); ++SectionIndex)
2684 {
2685 //Section material index have to be remap with the ReductionSettings.BaseLODModel SectionInfoMap to create
2686 //a valid new section info map for the reduced LOD.
2687 if (BaseLODModelSectionInfoMap.IsValidSection(ReductionSettings.BaseLODModel, UniqueMaterialIndex[SectionIndex]))
2688 {
2689 FMeshSectionInfo SectionInfo = BaseLODModelSectionInfoMap.Get(ReductionSettings.BaseLODModel, UniqueMaterialIndex[SectionIndex]);
2690 //Try to recuperate the valid data
2691 if (BaseLODModelSectionInfoMap.IsValidSection(LODIndex, SectionIndex))
2692 {
2693 //If the old LOD section was using the same Material copy the data
2694 FMeshSectionInfo OriginalLODSectionInfo = BaseLODModelSectionInfoMap.Get(LODIndex, SectionIndex);
2695 if (OriginalLODSectionInfo.MaterialIndex == SectionInfo.MaterialIndex)
2696 {
2697 SectionInfo.bCastShadow = OriginalLODSectionInfo.bCastShadow;
2698 SectionInfo.bEnableCollision = OriginalLODSectionInfo.bEnableCollision;
2699 }
2700 }
2701 //Copy the BaseLODModel section info to the reduce LODIndex.
2702 StaticMesh->GetSectionInfoMap().Set(LODIndex, SectionIndex, SectionInfo);
2703 }
2704 }
2705 }
2706 }
2707 }
2708
2709 if (LODMeshes[NumValidLODs].WedgeIndices.Num() > 0)
2710 {
2711 NumValidLODs++;
2712 }
2713 }
2714
2715 if (NumValidLODs < 1)
2716 {
2717 UE_LOG(LogMeshUtilities, Error, TEXT("Mesh reduction produced zero LODs."));
2718 return false;
2719 }
2720 Stage = EStage::Reduce;
2721 return true;
2722 }
2723
2724 bool GenerateRenderingMeshes(FMeshUtilities& MeshUtilities, FStaticMeshRenderData& OutRenderData)
2725 {
2726 check(Stage == EStage::Reduce);
2727 check(StaticMesh != nullptr);
2728
2729 TArray<FStaticMeshSourceModel>& InOutModels = StaticMesh->GetSourceModels();
2730 int32 ImportVersion = StaticMesh->ImportVersion;
2731
2732 // Generate per-LOD rendering data.
2733 OutRenderData.AllocateLODResources(NumValidLODs);
2734 for (int32 LODIndex = 0; LODIndex < NumValidLODs; ++LODIndex)
2735 {
2736 FStaticMeshLODResources& LODModel = OutRenderData.LODResources[LODIndex];
2737 FRawMesh& RawMesh = LODMeshes[LODIndex];
2738 LODModel.MaxDeviation = LODMaxDeviation[LODIndex];
2739
2740 TArray<FStaticMeshBuildVertex> Vertices;
2741 TArray<TArray<uint32> > PerSectionIndices;
2742
2743 TMap<uint32, uint32> MaterialToSectionMapping;
2744
2745 // Find out how many sections are in the mesh.
2746 TArray<int32> MaterialIndices;
2747 for ( const int32 MaterialIndex : RawMesh.FaceMaterialIndices )
2748 {
2749 // Find all unique material indices
2750 MaterialIndices.AddUnique(MaterialIndex);
2751 }
2752
2753 // Need X number of sections for X number of material indices
2754 //for (const int32 MaterialIndex : MaterialIndices)
2755 for ( int32 Index = 0; Index < MaterialIndices.Num(); ++Index)
2756 {
2757 const int32 MaterialIndex = MaterialIndices[Index];
2758 FStaticMeshSection* Section = new(LODModel.Sections) FStaticMeshSection();
2759 Section->MaterialIndex = MaterialIndex;
2760 if (ImportVersion < RemoveStaticMeshSkinxxWorkflow)
2761 {
2762 MaterialToSectionMapping.Add(MaterialIndex, MaterialIndex);
2763 }
2764 else
2765 {
2766 MaterialToSectionMapping.Add(MaterialIndex, Index);
2767 }
2768 new(PerSectionIndices)TArray<uint32>;
2769 }
2770
2771 // Build and cache optimize vertex and index buffers.
2772 {
2773 // TODO_STATICMESH: The wedge map is only valid for LODIndex 0 if no reduction has been performed.
2774 // TODO - write directly to TMemoryImageArray
2775 // We can compute an approximate one instead for other LODs.
2776 TArray<int32> TempWedgeMap;
2777 TArray<int32>& WedgeMap = InOutModels[LODIndex].ReductionSettings.PercentTriangles >= 1.0f ? LODModel.WedgeMap : TempWedgeMap;
2778 WedgeMap.Reset();
2779 float ComparisonThreshold = GetComparisonThreshold(LODBuildSettings[LODIndex]);
2780 MeshUtilities.BuildStaticMeshVertexAndIndexBuffers(Vertices, PerSectionIndices, WedgeMap, RawMesh, LODOverlappingCorners[LODIndex], MaterialToSectionMapping, ComparisonThreshold, LODBuildSettings[LODIndex].BuildScale3D, ImportVersion);
2781 check(WedgeMap.Num() == RawMesh.WedgeIndices.Num());
2782
2783 if (RawMesh.WedgeIndices.Num() < 100000 * 3)
2784 {
2785 MeshUtilities.CacheOptimizeVertexAndIndexBuffer(Vertices, PerSectionIndices, WedgeMap);
2786 check(WedgeMap.Num() == RawMesh.WedgeIndices.Num());
2787 }
2788 }
2789
2790 verifyf(Vertices.Num() != 0, TEXT("No valid vertices found for the mesh."));
2791
2792 // Initialize the vertex buffer.
2793 int32 NumTexCoords = ComputeNumTexCoords(RawMesh, MAX_STATIC_TEXCOORDS);
2794 LODModel.VertexBuffers.StaticMeshVertexBuffer.SetUseHighPrecisionTangentBasis(LODBuildSettings[LODIndex].bUseHighPrecisionTangentBasis);
2795 LODModel.VertexBuffers.StaticMeshVertexBuffer.SetUseFullPrecisionUVs(LODBuildSettings[LODIndex].bUseFullPrecisionUVs);
2796 LODModel.VertexBuffers.StaticMeshVertexBuffer.Init(Vertices, NumTexCoords);
2797 LODModel.VertexBuffers.PositionVertexBuffer.Init(Vertices);
2798 LODModel.VertexBuffers.ColorVertexBuffer.Init(Vertices);
2799
2800 // Concatenate the per-section index buffers.
2801 TArray<uint32> CombinedIndices;
2802 bool bNeeds32BitIndices = false;
2803 for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
2804 {
2805 FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
2806 TArray<uint32> const& SectionIndices = PerSectionIndices[SectionIndex];
2807 Section.FirstIndex = 0;
2808 Section.NumTriangles = 0;
2809 Section.MinVertexIndex = 0;
2810 Section.MaxVertexIndex = 0;
2811
2812 if (SectionIndices.Num())
2813 {
2814 Section.FirstIndex = CombinedIndices.Num();
2815 Section.NumTriangles = SectionIndices.Num() / 3;
2816
2817 CombinedIndices.AddUninitialized(SectionIndices.Num());
2818 uint32* DestPtr = &CombinedIndices[Section.FirstIndex];
2819 uint32 const* SrcPtr = SectionIndices.GetData();
2820
2821 Section.MinVertexIndex = *SrcPtr;
2822 Section.MaxVertexIndex = *SrcPtr;
2823
2824 for (int32 Index = 0; Index < SectionIndices.Num(); Index++)
2825 {
2826 uint32 VertIndex = *SrcPtr++;
2827
2828 bNeeds32BitIndices |= (VertIndex > MAX_uint16);
2829 Section.MinVertexIndex = FMath::Min<uint32>(VertIndex, Section.MinVertexIndex);
2830 Section.MaxVertexIndex = FMath::Max<uint32>(VertIndex, Section.MaxVertexIndex);
2831 *DestPtr++ = VertIndex;
2832 }
2833 }
2834 }
2835 LODModel.IndexBuffer.SetIndices(CombinedIndices, bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit);
2836
2837 // Build the reversed index buffer.
2838 if (LODModel.AdditionalIndexBuffers && InOutModels[0].BuildSettings.bBuildReversedIndexBuffer)
2839 {
2840 TArray<uint32> InversedIndices;
2841 const int32 IndexCount = CombinedIndices.Num();
2842 InversedIndices.AddUninitialized(IndexCount);
2843
2844 for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); ++SectionIndex)
2845 {
2846 const FStaticMeshSection& SectionInfo = LODModel.Sections[SectionIndex];
2847 const int32 SectionIndexCount = SectionInfo.NumTriangles * 3;
2848
2849 for (int32 i = 0; i < SectionIndexCount; ++i)
2850 {
2851 InversedIndices[SectionInfo.FirstIndex + i] = CombinedIndices[SectionInfo.FirstIndex + SectionIndexCount - 1 - i];
2852 }
2853 }
2854 LODModel.AdditionalIndexBuffers->ReversedIndexBuffer.SetIndices(InversedIndices, bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit);
2855 }
2856
2857 // Build the depth-only index buffer.
2858 TArray<uint32> DepthOnlyIndices;
2859 {
2860 BuildDepthOnlyIndexBuffer(
2861 DepthOnlyIndices,
2862 Vertices,
2863 CombinedIndices,
2864 LODModel.Sections
2865 );
2866
2867 if (DepthOnlyIndices.Num() < 50000 * 3)
2868 {
2869 MeshUtilities.CacheOptimizeIndexBuffer(DepthOnlyIndices);
2870 }
2871
2872 LODModel.DepthOnlyIndexBuffer.SetIndices(DepthOnlyIndices, bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit);
2873 }
2874
2875 // Build the inversed depth only index buffer.
2876 if (LODModel.AdditionalIndexBuffers && InOutModels[0].BuildSettings.bBuildReversedIndexBuffer)
2877 {
2878 TArray<uint32> ReversedDepthOnlyIndices;
2879 const int32 IndexCount = DepthOnlyIndices.Num();
2880 ReversedDepthOnlyIndices.AddUninitialized(IndexCount);
2881 for (int32 i = 0; i < IndexCount; ++i)
2882 {
2883 ReversedDepthOnlyIndices[i] = DepthOnlyIndices[IndexCount - 1 - i];
2884 }
2885 LODModel.AdditionalIndexBuffers->ReversedDepthOnlyIndexBuffer.SetIndices(ReversedDepthOnlyIndices, bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit);
2886 }
2887
2888 // Build a list of wireframe edges in the static mesh.
2889 if (LODModel.AdditionalIndexBuffers)
2890 {
2891 TArray<FMeshEdgeDef> Edges;
2892 TArray<uint32> WireframeIndices;
2893
2894 FStaticMeshEdgeBuilder(CombinedIndices, Vertices, Edges).FindEdges();
2895 WireframeIndices.Empty(2 * Edges.Num());
2896 for (int32 EdgeIndex = 0; EdgeIndex < Edges.Num(); EdgeIndex++)
2897 {
2898 FMeshEdgeDef& Edge = Edges[EdgeIndex];
2899 WireframeIndices.Add(Edge.Vertices[0]);
2900 WireframeIndices.Add(Edge.Vertices[1]);
2901 }
2902 LODModel.AdditionalIndexBuffers->WireframeIndexBuffer.SetIndices(WireframeIndices, bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit);
2903 }
2904
2905 // Build the adjacency index buffer used for tessellation.
2906 if (LODModel.AdditionalIndexBuffers && InOutModels[0].BuildSettings.bBuildAdjacencyBuffer)
2907 {
2908 TArray<uint32> AdjacencyIndices;
2909
2910 BuildOptimizationThirdParty::NvTriStripHelper::BuildStaticAdjacencyIndexBuffer(
2911 LODModel.VertexBuffers.PositionVertexBuffer,
2912 LODModel.VertexBuffers.StaticMeshVertexBuffer,
2913 CombinedIndices,
2914 AdjacencyIndices
2915 );
2916 LODModel.AdditionalIndexBuffers->AdjacencyIndexBuffer.SetIndices(AdjacencyIndices, bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit);
2917 }
2918 }
2919
2920 // Copy the original material indices to fixup meshes before compacting of materials was done.
2921 if (NumValidLODs > 0)
2922 {
2923 OutRenderData.MaterialIndexToImportIndex = LODMeshes[0].MaterialIndexToImportIndex;
2924 }
2925
2926 // Calculate the bounding box.
2927 FBox BoundingBox(ForceInit);
2928 FPositionVertexBuffer& BasePositionVertexBuffer = OutRenderData.LODResources[0].VertexBuffers.PositionVertexBuffer;
2929 for (uint32 VertexIndex = 0; VertexIndex < BasePositionVertexBuffer.GetNumVertices(); VertexIndex++)
2930 {
2931 BoundingBox += BasePositionVertexBuffer.VertexPosition(VertexIndex);
2932 }
2933 BoundingBox.GetCenterAndExtents(OutRenderData.Bounds.Origin, OutRenderData.Bounds.BoxExtent);
2934
2935 // Calculate the bounding sphere, using the center of the bounding box as the origin.
2936 OutRenderData.Bounds.SphereRadius = 0.0f;
2937 for (uint32 VertexIndex = 0; VertexIndex < BasePositionVertexBuffer.GetNumVertices(); VertexIndex++)
2938 {
2939 OutRenderData.Bounds.SphereRadius = FMath::Max(
2940 (BasePositionVertexBuffer.VertexPosition(VertexIndex) - OutRenderData.Bounds.Origin).Size(),
2941 OutRenderData.Bounds.SphereRadius
2942 );
2943 }
2944
2945 Stage = EStage::GenerateRendering;
2946 return true;
2947 }
2948
2949 bool ReplaceRawMeshModels()
2950 {
2951 check(Stage == EStage::Reduce);
2952 check(StaticMesh != nullptr);
2953
2954 TArray<FStaticMeshSourceModel>& SourceModels = StaticMesh->GetSourceModels();
2955
2956 check(HasRawMesh[0]);
2957 check(SourceModels.Num() >= NumValidLODs);
2958 bool bDirty = false;
2959 for (int32 Index = 1; Index < NumValidLODs; ++Index)
2960 {
2961 if (!HasRawMesh[Index])
2962 {
2963 SourceModels[Index].SaveRawMesh(LODMeshes[Index]);
2964 bDirty = true;
2965 }
2966 }
2967
2968 Stage = EStage::ReplaceRaw;
2969 return true;
2970 }
2971
2972private:
2973 enum class EStage
2974 {
2975 Uninit,
2976 Gathered,
2977 Reduce,
2978 GenerateRendering,
2979 ReplaceRaw,
2980 };
2981
2982 EStage Stage;
2983
2984 int32 NumValidLODs;
2985
2986 TIndirectArray<FRawMesh> LODMeshes;
2987 TIndirectArray<FOverlappingCorners> LODOverlappingCorners;
2988 float LODMaxDeviation[MAX_STATIC_MESH_LODS];
2989 FMeshBuildSettings LODBuildSettings[MAX_STATIC_MESH_LODS];
2990 bool HasRawMesh[MAX_STATIC_MESH_LODS];
2991 UStaticMesh* StaticMesh;
2992};
2993
2994bool FMeshUtilities::BuildStaticMesh(FStaticMeshRenderData& OutRenderData, UStaticMesh* StaticMesh, const FStaticMeshLODGroup& LODGroup)
2995{
2996 TArray<FStaticMeshSourceModel>& SourceModels = StaticMesh->GetSourceModels();
2997 int32 LightmapUVVersion = StaticMesh->LightmapUVVersion;
2998 int32 ImportVersion = StaticMesh->ImportVersion;
2999
3000 IMeshReductionManagerModule& Module = FModuleManager::Get().LoadModuleChecked<IMeshReductionManagerModule>("MeshReductionInterface");
3001 FStaticMeshUtilityBuilder Builder(StaticMesh);
3002 if (!Builder.GatherSourceMeshesPerLOD(Module.GetStaticMeshReductionInterface()))
3003 {
3004 return false;
3005 }
3006
3007 TArray<bool> WasReduced;
3008 WasReduced.AddZeroed(SourceModels.Num());
3009 if (!Builder.ReduceLODs(LODGroup, Module.GetStaticMeshReductionInterface(), WasReduced))
3010 {
3011 return false;
3012 }
3013
3014 return Builder.GenerateRenderingMeshes(*this, OutRenderData);
3015}
3016
3017bool FMeshUtilities::GenerateStaticMeshLODs(UStaticMesh* StaticMesh, const FStaticMeshLODGroup& LODGroup)
3018{
3019 TArray<FStaticMeshSourceModel>& Models = StaticMesh->GetSourceModels();
3020 int32 LightmapUVVersion = StaticMesh->LightmapUVVersion;
3021
3022 FStaticMeshUtilityBuilder Builder(StaticMesh);
3023 IMeshReductionManagerModule& Module = FModuleManager::Get().LoadModuleChecked<IMeshReductionManagerModule>("MeshReductionInterface");
3024 if (!Builder.GatherSourceMeshesPerLOD(Module.GetStaticMeshReductionInterface()))
3025 {
3026 return false;
3027 }
3028
3029 TArray<bool> WasReduced;
3030 WasReduced.AddZeroed(Models.Num());
3031 if (!Builder.ReduceLODs(LODGroup, Module.GetStaticMeshReductionInterface(), WasReduced))
3032 {
3033 return false;
3034 }
3035
3036 if (WasReduced.Contains(true))
3037 {
3038 return Builder.ReplaceRawMeshModels();
3039 }
3040
3041 return false;
3042}
3043
3044class IMeshBuildData
3045{
3046public:
3047 virtual ~IMeshBuildData() { }
3048
3049 virtual uint32 GetWedgeIndex(uint32 FaceIndex, uint32 TriIndex) = 0;
3050 virtual uint32 GetVertexIndex(uint32 WedgeIndex) = 0;
3051 virtual uint32 GetVertexIndex(uint32 FaceIndex, uint32 TriIndex) = 0;
3052 virtual FVector GetVertexPosition(uint32 WedgeIndex) = 0;
3053 virtual FVector GetVertexPosition(uint32 FaceIndex, uint32 TriIndex) = 0;
3054 virtual FVector2D GetVertexUV(uint32 FaceIndex, uint32 TriIndex, uint32 UVIndex) = 0;
3055 virtual uint32 GetFaceSmoothingGroups(uint32 FaceIndex) = 0;
3056
3057 virtual uint32 GetNumFaces() = 0;
3058 virtual uint32 GetNumWedges() = 0;
3059
3060 virtual TArray<FVector>& GetTangentArray(uint32 Axis) = 0;
3061 virtual void ValidateTangentArraySize() = 0;
3062
3063 virtual SMikkTSpaceInterface* GetMikkTInterface() = 0;
3064 virtual void* GetMikkTUserData() = 0;
3065
3066 const IMeshUtilities::MeshBuildOptions& BuildOptions;
3067 TArray<FText>* OutWarningMessages;
3068 TArray<FName>* OutWarningNames;
3069 bool bTooManyVerts;
3070
3071protected:
3072 IMeshBuildData(
3073 const IMeshUtilities::MeshBuildOptions& InBuildOptions,
3074 TArray<FText>* InWarningMessages,
3075 TArray<FName>* InWarningNames)
3076 : BuildOptions(InBuildOptions)
3077 , OutWarningMessages(InWarningMessages)
3078 , OutWarningNames(InWarningNames)
3079 , bTooManyVerts(false)
3080 {
3081 }
3082};
3083
3084class SkeletalMeshBuildData final : public IMeshBuildData
3085{
3086public:
3087 SkeletalMeshBuildData(
3088 FSkeletalMeshLODModel& InLODModel,
3089 const FReferenceSkeleton& InRefSkeleton,
3090 const TArray<SkeletalMeshImportData::FVertInfluence>& InInfluences,
3091 const TArray<SkeletalMeshImportData::FMeshWedge>& InWedges,
3092 const TArray<SkeletalMeshImportData::FMeshFace>& InFaces,
3093 const TArray<FVector>& InPoints,
3094 const TArray<int32>& InPointToOriginalMap,
3095 const IMeshUtilities::MeshBuildOptions& InBuildOptions,
3096 TArray<FText>* InWarningMessages,
3097 TArray<FName>* InWarningNames)
3098 : IMeshBuildData(InBuildOptions, InWarningMessages, InWarningNames)
3099 , MikkTUserData(InWedges, InFaces, InPoints, InBuildOptions.bComputeNormals, TangentX, TangentY, TangentZ)
3100 , LODModel(InLODModel)
3101 , RefSkeleton(InRefSkeleton)
3102 , Influences(InInfluences)
3103 , Wedges(InWedges)
3104 , Faces(InFaces)
3105 , Points(InPoints)
3106 , PointToOriginalMap(InPointToOriginalMap)
3107 {
3108 MikkTInterface.m_getNormal = MikkGetNormal_Skeletal;
3109 MikkTInterface.m_getNumFaces = MikkGetNumFaces_Skeletal;
3110 MikkTInterface.m_getNumVerticesOfFace = MikkGetNumVertsOfFace_Skeletal;
3111 MikkTInterface.m_getPosition = MikkGetPosition_Skeletal;
3112 MikkTInterface.m_getTexCoord = MikkGetTexCoord_Skeletal;
3113 MikkTInterface.m_setTSpaceBasic = MikkSetTSpaceBasic_Skeletal;
3114 MikkTInterface.m_setTSpace = nullptr;
3115
3116 //Fill the NTBs information
3117 if (!InBuildOptions.bComputeNormals || !InBuildOptions.bComputeTangents)
3118 {
3119 if (!InBuildOptions.bComputeTangents)
3120 {
3121 TangentX.AddZeroed(Wedges.Num());
3122 TangentY.AddZeroed(Wedges.Num());
3123 }
3124
3125 if (!InBuildOptions.bComputeNormals)
3126 {
3127 TangentZ.AddZeroed(Wedges.Num());
3128 }
3129
3130 for (const SkeletalMeshImportData::FMeshFace& MeshFace : Faces)
3131 {
3132 for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
3133 {
3134 uint32 WedgeIndex = MeshFace.iWedge[CornerIndex];
3135 if (!InBuildOptions.bComputeTangents)
3136 {
3137 TangentX[WedgeIndex] = MeshFace.TangentX[CornerIndex];
3138 TangentY[WedgeIndex] = MeshFace.TangentY[CornerIndex];
3139 }
3140 if (!InBuildOptions.bComputeNormals)
3141 {
3142 TangentZ[WedgeIndex] = MeshFace.TangentZ[CornerIndex];
3143 }
3144 }
3145 }
3146 }
3147 }
3148
3149 virtual uint32 GetWedgeIndex(uint32 FaceIndex, uint32 TriIndex) override
3150 {
3151 return Faces[FaceIndex].iWedge[TriIndex];
3152 }
3153
3154 virtual uint32 GetVertexIndex(uint32 WedgeIndex) override
3155 {
3156 return Wedges[WedgeIndex].iVertex;
3157 }
3158
3159 virtual uint32 GetVertexIndex(uint32 FaceIndex, uint32 TriIndex) override
3160 {
3161 return Wedges[Faces[FaceIndex].iWedge[TriIndex]].iVertex;
3162 }
3163
3164 virtual FVector GetVertexPosition(uint32 WedgeIndex) override
3165 {
3166 return Points[Wedges[WedgeIndex].iVertex];
3167 }
3168
3169 virtual FVector GetVertexPosition(uint32 FaceIndex, uint32 TriIndex) override
3170 {
3171 return Points[Wedges[Faces[FaceIndex].iWedge[TriIndex]].iVertex];
3172 }
3173
3174 virtual FVector2D GetVertexUV(uint32 FaceIndex, uint32 TriIndex, uint32 UVIndex) override
3175 {
3176 return Wedges[Faces[FaceIndex].iWedge[TriIndex]].UVs[UVIndex];
3177 }
3178
3179 virtual uint32 GetFaceSmoothingGroups(uint32 FaceIndex)
3180 {
3181 return Faces[FaceIndex].SmoothingGroups;
3182 }
3183
3184 virtual uint32 GetNumFaces() override
3185 {
3186 return Faces.Num();
3187 }
3188
3189 virtual uint32 GetNumWedges() override
3190 {
3191 return Wedges.Num();
3192 }
3193
3194 virtual TArray<FVector>& GetTangentArray(uint32 Axis) override
3195 {
3196 if (Axis == 0)
3197 {
3198 return TangentX;
3199 }
3200 else if (Axis == 1)
3201 {
3202 return TangentY;
3203 }
3204
3205 return TangentZ;
3206 }
3207
3208 virtual void ValidateTangentArraySize() override
3209 {
3210 check(TangentX.Num() == Wedges.Num());
3211 check(TangentY.Num() == Wedges.Num());
3212 check(TangentZ.Num() == Wedges.Num());
3213 }
3214
3215 virtual SMikkTSpaceInterface* GetMikkTInterface() override
3216 {
3217 return &MikkTInterface;
3218 }
3219
3220 virtual void* GetMikkTUserData() override
3221 {
3222 return (void*)&MikkTUserData;
3223 }
3224
3225 TArray<FVector> TangentX;
3226 TArray<FVector> TangentY;
3227 TArray<FVector> TangentZ;
3228 TArray<FSkinnedMeshChunk*> Chunks;
3229
3230 SMikkTSpaceInterface MikkTInterface;
3231 MikkTSpace_Skeletal_Mesh MikkTUserData;
3232
3233 FSkeletalMeshLODModel& LODModel;
3234 const FReferenceSkeleton& RefSkeleton;
3235 const TArray<SkeletalMeshImportData::FVertInfluence>& Influences;
3236 const TArray<SkeletalMeshImportData::FMeshWedge>& Wedges;
3237 const TArray<SkeletalMeshImportData::FMeshFace>& Faces;
3238 const TArray<FVector>& Points;
3239 const TArray<int32>& PointToOriginalMap;
3240};
3241
3242class FSkeletalMeshUtilityBuilder
3243{
3244public:
3245 FSkeletalMeshUtilityBuilder()
3246 : Stage(EStage::Uninit)
3247 {
3248 }
3249
3250public:
3251 void Skeletal_FindOverlappingCorners(
3252 FOverlappingCorners& OutOverlappingCorners,
3253 IMeshBuildData* BuildData,
3254 float ComparisonThreshold
3255 )
3256 {
3257 int32 NumFaces = BuildData->GetNumFaces();
3258 int32 NumWedges = BuildData->GetNumWedges();
3259 check(NumFaces * 3 <= NumWedges);
3260
3261 // Create a list of vertex Z/index pairs
3262 TArray<FIndexAndZ> VertIndexAndZ;
3263 VertIndexAndZ.Empty(NumWedges);
3264 for (int32 FaceIndex = 0; FaceIndex < NumFaces; FaceIndex++)
3265 {
3266 for (int32 TriIndex = 0; TriIndex < 3; ++TriIndex)
3267 {
3268 uint32 Index = BuildData->GetWedgeIndex(FaceIndex, TriIndex);
3269 new(VertIndexAndZ)FIndexAndZ(Index, BuildData->GetVertexPosition(Index));
3270 }
3271 }
3272
3273 // Sort the vertices by z value
3274 VertIndexAndZ.Sort(FCompareIndexAndZ());
3275
3276 OutOverlappingCorners.Init(NumWedges);
3277
3278 // Search for duplicates, quickly!
3279 for (int32 i = 0; i < VertIndexAndZ.Num(); i++)
3280 {
3281 // only need to search forward, since we add pairs both ways
3282 for (int32 j = i + 1; j < VertIndexAndZ.Num(); j++)
3283 {
3284 if (FMath::Abs(VertIndexAndZ[j].Z - VertIndexAndZ[i].Z) > ComparisonThreshold)
3285 break; // can't be any more dups
3286
3287 FVector PositionA = BuildData->GetVertexPosition(VertIndexAndZ[i].Index);
3288 FVector PositionB = BuildData->GetVertexPosition(VertIndexAndZ[j].Index);
3289
3290 if (PointsEqual(PositionA, PositionB, ComparisonThreshold))
3291 {
3292 OutOverlappingCorners.Add(VertIndexAndZ[i].Index, VertIndexAndZ[j].Index);
3293 }
3294 }
3295 }
3296
3297 OutOverlappingCorners.FinishAdding();
3298 }
3299
3300 void Skeletal_ComputeTriangleTangents(
3301 TArray<FVector>& TriangleTangentX,
3302 TArray<FVector>& TriangleTangentY,
3303 TArray<FVector>& TriangleTangentZ,
3304 IMeshBuildData* BuildData,
3305 float ComparisonThreshold
3306 )
3307 {
3308 int32 NumTriangles = BuildData->GetNumFaces();
3309 TriangleTangentX.Empty(NumTriangles);
3310 TriangleTangentY.Empty(NumTriangles);
3311 TriangleTangentZ.Empty(NumTriangles);
3312
3313 //Currently GetSafeNormal do not support 0.0f threshold properly
3314 float RealComparisonThreshold = FMath::Max(ComparisonThreshold, FLT_MIN);
3315
3316 for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; TriangleIndex++)
3317 {
3318 const int32 UVIndex = 0;
3319 FVector P[3];
3320
3321 for (int32 i = 0; i < 3; ++i)
3322 {
3323 P[i] = BuildData->GetVertexPosition(TriangleIndex, i);
3324 }
3325
3326 //get safe normal should have return a valid normalized vector or a zero vector.
3327 const FVector Normal = ((P[1] - P[2]) ^ (P[0] - P[2])).GetSafeNormal(RealComparisonThreshold);
3328 //Avoid doing orthonormal vector from a degenerated triangle.
3329 if (!Normal.IsNearlyZero(FLT_MIN))
3330 {
3331 FMatrix ParameterToLocal(
3332 FPlane(P[1].X - P[0].X, P[1].Y - P[0].Y, P[1].Z - P[0].Z, 0),
3333 FPlane(P[2].X - P[0].X, P[2].Y - P[0].Y, P[2].Z - P[0].Z, 0),
3334 FPlane(P[0].X, P[0].Y, P[0].Z, 0),
3335 FPlane(0, 0, 0, 1)
3336 );
3337
3338 FVector2D T1 = BuildData->GetVertexUV(TriangleIndex, 0, UVIndex);
3339 FVector2D T2 = BuildData->GetVertexUV(TriangleIndex, 1, UVIndex);
3340 FVector2D T3 = BuildData->GetVertexUV(TriangleIndex, 2, UVIndex);
3341 FMatrix ParameterToTexture(
3342 FPlane(T2.X - T1.X, T2.Y - T1.Y, 0, 0),
3343 FPlane(T3.X - T1.X, T3.Y - T1.Y, 0, 0),
3344 FPlane(T1.X, T1.Y, 1, 0),
3345 FPlane(0, 0, 0, 1)
3346 );
3347
3348 // Use InverseSlow to catch singular matrices. Inverse can miss this sometimes.
3349 const FMatrix TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal;
3350
3351 TriangleTangentX.Add(TextureToLocal.TransformVector(FVector(1, 0, 0)).GetSafeNormal());
3352 TriangleTangentY.Add(TextureToLocal.TransformVector(FVector(0, 1, 0)).GetSafeNormal());
3353 TriangleTangentZ.Add(Normal);
3354
3355 FVector::CreateOrthonormalBasis(
3356 TriangleTangentX[TriangleIndex],
3357 TriangleTangentY[TriangleIndex],
3358 TriangleTangentZ[TriangleIndex]
3359 );
3360
3361 if (TriangleTangentX[TriangleIndex].IsNearlyZero() || TriangleTangentX[TriangleIndex].ContainsNaN()
3362 || TriangleTangentY[TriangleIndex].IsNearlyZero() || TriangleTangentY[TriangleIndex].ContainsNaN()
3363 || TriangleTangentZ[TriangleIndex].IsNearlyZero() || TriangleTangentZ[TriangleIndex].ContainsNaN())
3364 {
3365 TriangleTangentX[TriangleIndex] = FVector::ZeroVector;
3366 TriangleTangentY[TriangleIndex] = FVector::ZeroVector;
3367 TriangleTangentZ[TriangleIndex] = FVector::ZeroVector;
3368 }
3369 }
3370 else
3371 {
3372 //Add zero tangents and normal for this triangle, this is like weighting it to zero when we compute the vertex normal
3373 //But we need the triangle to correctly connect other neighbourg triangles
3374 TriangleTangentX.Add(FVector::ZeroVector);
3375 TriangleTangentY.Add(FVector::ZeroVector);
3376 TriangleTangentZ.Add(FVector::ZeroVector);
3377 }
3378 }
3379 }
3380
3381 //This function add every triangles connected to the triangle queue.
3382 //A connected triangle pair must share at least 1 vertex between the two triangles.
3383 //If bConnectByEdge is true, the connected triangle must share at least one edge (two vertex index)
3384 void AddAdjacentFace(IMeshBuildData* BuildData, TBitArray<>& FaceAdded, TMap<int32, TArray<int32>>& VertexIndexToAdjacentFaces, int32 FaceIndex, TArray<int32>& TriangleQueue, const bool bConnectByEdge)
3385 {
3386 int32 NumFaces = (int32)BuildData->GetNumFaces();
3387 check(FaceAdded.Num() == NumFaces);
3388
3389 TMap<int32, int32> AdjacentFaceCommonVertices;
3390 for (int32 Corner = 0; Corner < 3; Corner++)
3391 {
3392 int32 VertexIndex = BuildData->GetVertexIndex(FaceIndex, Corner);
3393 TArray<int32>& AdjacentFaces = VertexIndexToAdjacentFaces.FindChecked(VertexIndex);
3394 for (int32 AdjacentFaceArrayIndex = 0; AdjacentFaceArrayIndex < AdjacentFaces.Num(); ++AdjacentFaceArrayIndex)
3395 {
3396 int32 AdjacentFaceIndex = AdjacentFaces[AdjacentFaceArrayIndex];
3397 if (!FaceAdded[AdjacentFaceIndex] && AdjacentFaceIndex != FaceIndex)
3398 {
3399 bool bAddConnected = !bConnectByEdge;
3400 if (bConnectByEdge)
3401 {
3402 int32& AdjacentFaceCommonVerticeCount = AdjacentFaceCommonVertices.FindOrAdd(AdjacentFaceIndex);
3403 AdjacentFaceCommonVerticeCount++;
3404 //Is the connected triangles share 2 vertex index (one edge) not only one vertex
3405 bAddConnected = AdjacentFaceCommonVerticeCount > 1;
3406 }
3407
3408 if (bAddConnected)
3409 {
3410 TriangleQueue.Add(AdjacentFaceIndex);
3411 //Add the face only once by marking the face has computed
3412 FaceAdded[AdjacentFaceIndex] = true;
3413 }
3414 }
3415 }
3416 }
3417 }
3418
3419 //Fill FaceIndexToPatchIndex so every triangle know is unique island patch index.
3420 //We need to respect the island when we use the smooth group to compute the normals.
3421 //Each island patch have its own smoothgroup data, there is no triangle connectivity possible between island patch.
3422 //@Param bConnectByEdge: If true we need at least 2 vertex index (one edge) to connect 2 triangle. If false we just need one vertex index (bowtie)
3423 void Skeletal_FillPolygonPatch(IMeshBuildData* BuildData, TArray<int32>& FaceIndexToPatchIndex, const bool bConnectByEdge)
3424 {
3425 int32 NumTriangles = BuildData->GetNumFaces();
3426 check(FaceIndexToPatchIndex.Num() == NumTriangles);
3427
3428 int32 PatchIndex = 0;
3429
3430 TMap<int32, TArray<int32>> VertexIndexToAdjacentFaces;
3431 VertexIndexToAdjacentFaces.Reserve(BuildData->GetNumFaces()*2);
3432 for (int32 FaceIndex = 0; FaceIndex < NumTriangles; ++FaceIndex)
3433 {
3434 int32 WedgeOffset = FaceIndex * 3;
3435 for (int32 Corner = 0; Corner < 3; Corner++)
3436 {
3437 int32 VertexIndex = BuildData->GetVertexIndex(FaceIndex, Corner);
3438 TArray<int32>& AdjacentFaces = VertexIndexToAdjacentFaces.FindOrAdd(VertexIndex);
3439 AdjacentFaces.AddUnique(FaceIndex);
3440 }
3441 }
3442
3443 //Mark added face so we do not add them more then once
3444 TBitArray<> FaceAdded;
3445 FaceAdded.Init(false, NumTriangles);
3446
3447 TArray<int32> TriangleQueue;
3448 TriangleQueue.Reserve(100);
3449 for (int32 FaceIndex = 0; FaceIndex < NumTriangles; ++FaceIndex)
3450 {
3451 if (FaceAdded[FaceIndex])
3452 {
3453 continue;
3454 }
3455 TriangleQueue.Reset();
3456 TriangleQueue.Add(FaceIndex); //Use a queue to avoid recursive function
3457 FaceAdded[FaceIndex] = true;
3458 while (TriangleQueue.Num() > 0)
3459 {
3460 int32 CurrentTriangleIndex = TriangleQueue.Pop(false);
3461 FaceIndexToPatchIndex[CurrentTriangleIndex] = PatchIndex;
3462 AddAdjacentFace(BuildData, FaceAdded, VertexIndexToAdjacentFaces, CurrentTriangleIndex, TriangleQueue, bConnectByEdge);
3463 }
3464 PatchIndex++;
3465 }
3466 }
3467
3468 bool IsTriangleMirror(IMeshBuildData* BuildData, const TArray<FVector>& TriangleTangentZ, const uint32 FaceIdxA, const uint32 FaceIdxB)
3469 {
3470 if (FaceIdxA == FaceIdxB)
3471 {
3472 return false;
3473 }
3474 for (int32 CornerA = 0; CornerA < 3; ++CornerA)
3475 {
3476 const FVector& CornerAPosition = BuildData->GetVertexPosition((FaceIdxA * 3) + CornerA);
3477 bool bFoundMatch = false;
3478 for (int32 CornerB = 0; CornerB < 3; ++CornerB)
3479 {
3480 const FVector& CornerBPosition = BuildData->GetVertexPosition((FaceIdxB * 3) + CornerB);
3481 if (PointsEqual(CornerAPosition, CornerBPosition, BuildData->BuildOptions.OverlappingThresholds))
3482 {
3483 bFoundMatch = true;
3484 break;
3485 }
3486 }
3487
3488 if (!bFoundMatch)
3489 {
3490 return false;
3491 }
3492 }
3493 //Check if the triangles normals are opposite and parallel. Dot product equal -1.0f
3494 if (FMath::IsNearlyEqual(FVector::DotProduct(TriangleTangentZ[FaceIdxA], TriangleTangentZ[FaceIdxB]), -1.0f, KINDA_SMALL_NUMBER))
3495 {
3496 return true;
3497 }
3498 return false;
3499 }
3500
3501 void Skeletal_ComputeTangents(
3502 IMeshBuildData* BuildData,
3503 const FOverlappingCorners& OverlappingCorners
3504 )
3505 {
3506 bool bBlendOverlappingNormals = true;
3507 bool bIgnoreDegenerateTriangles = BuildData->BuildOptions.bRemoveDegenerateTriangles;
3508 bool bComputeWeightedNormals = BuildData->BuildOptions.bComputeWeightedNormals;
3509 bool bUseMikktSpace = BuildData->BuildOptions.bUseMikkTSpace && (BuildData->BuildOptions.bComputeNormals || BuildData->BuildOptions.bComputeTangents);
3510
3511 int32 NumFaces = BuildData->GetNumFaces();
3512 int32 NumWedges = BuildData->GetNumWedges();
3513 check(NumFaces * 3 <= NumWedges);
3514
3515 // Compute per-triangle tangents.
3516 TArray<FVector> TriangleTangentX;
3517 TArray<FVector> TriangleTangentY;
3518 TArray<FVector> TriangleTangentZ;
3519
3520 Skeletal_ComputeTriangleTangents(
3521 TriangleTangentX,
3522 TriangleTangentY,
3523 TriangleTangentZ,
3524 BuildData,
3525 bIgnoreDegenerateTriangles ? SMALL_NUMBER : FLT_MIN
3526 );
3527
3528 TArray<int32> FaceIndexToPatchIndex;
3529 FaceIndexToPatchIndex.AddZeroed(NumFaces);
3530 //Since we use triangle normals to compute the vertex normal, we need a full edge connected (2 vertex component per triangle)
3531 const bool bConnectByEdge = true;
3532 Skeletal_FillPolygonPatch(BuildData, FaceIndexToPatchIndex, bConnectByEdge);
3533
3534 TArray<FVector>& WedgeTangentX = BuildData->GetTangentArray(0);
3535 TArray<FVector>& WedgeTangentY = BuildData->GetTangentArray(1);
3536 TArray<FVector>& WedgeTangentZ = BuildData->GetTangentArray(2);
3537
3538 // Declare these out here to avoid reallocations.
3539 TArray<FFanFace> RelevantFacesForCorner[3];
3540 TArray<int32> AdjacentFaces;
3541
3542 // Allocate storage for tangents and normal if none were provided.
3543 if (WedgeTangentX.Num() != NumWedges)
3544 {
3545 WedgeTangentX.Empty(NumWedges);
3546 WedgeTangentX.AddZeroed(NumWedges);
3547 }
3548 if (WedgeTangentY.Num() != NumWedges)
3549 {
3550 WedgeTangentY.Empty(NumWedges);
3551 WedgeTangentY.AddZeroed(NumWedges);
3552 }
3553 if (WedgeTangentZ.Num() != NumWedges)
3554 {
3555 WedgeTangentZ.Empty(NumWedges);
3556 WedgeTangentZ.AddZeroed(NumWedges);
3557 }
3558
3559 bool bIsZeroLengthNormalErrorMessageDisplayed = false;
3560 // we need to calculate normals for MikkTSpace
3561 for (int32 FaceIndex = 0; FaceIndex < NumFaces; FaceIndex++)
3562 {
3563 int32 PatchIndex = FaceIndexToPatchIndex[FaceIndex];
3564 int32 WedgeOffset = FaceIndex * 3;
3565 FVector CornerPositions[3];
3566 FVector CornerTangentX[3];
3567 FVector CornerTangentY[3];
3568 FVector CornerNormal[3];
3569
3570 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
3571 {
3572 CornerTangentX[CornerIndex] = FVector::ZeroVector;
3573 CornerTangentY[CornerIndex] = FVector::ZeroVector;
3574 CornerNormal[CornerIndex] = FVector::ZeroVector;
3575 CornerPositions[CornerIndex] = BuildData->GetVertexPosition(FaceIndex, CornerIndex);
3576 RelevantFacesForCorner[CornerIndex].Reset();
3577 }
3578
3579 // Don't process degenerate triangles.
3580 if (PointsEqual(CornerPositions[0], CornerPositions[1], BuildData->BuildOptions.OverlappingThresholds)
3581 || PointsEqual(CornerPositions[0], CornerPositions[2], BuildData->BuildOptions.OverlappingThresholds)
3582 || PointsEqual(CornerPositions[1], CornerPositions[2], BuildData->BuildOptions.OverlappingThresholds))
3583 {
3584 continue;
3585 }
3586
3587 // No need to process triangles if tangents already exist.
3588 bool bCornerHasNormal[3] = { 0 };
3589 bool bCornerHasTangents[3] = { 0 };
3590 bool bSkipNTB = true;
3591 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
3592 {
3593 bCornerHasNormal[CornerIndex] = !WedgeTangentZ[WedgeOffset + CornerIndex].IsNearlyZero();
3594 bCornerHasTangents[CornerIndex] = !WedgeTangentX[WedgeOffset + CornerIndex].IsNearlyZero() && !WedgeTangentY[WedgeOffset + CornerIndex].IsNearlyZero();
3595
3596 //If we want to compute mikkt we dont check tangents to skip this corner
3597 if (!bCornerHasNormal[CornerIndex] || (!bUseMikktSpace && !bCornerHasTangents[CornerIndex]))
3598 {
3599 bSkipNTB = false;
3600 }
3601 }
3602 if (bSkipNTB)
3603 {
3604 continue;
3605 }
3606
3607 // Calculate smooth vertex normals.
3608 float Determinant = FVector::Triple(
3609 TriangleTangentX[FaceIndex],
3610 TriangleTangentY[FaceIndex],
3611 TriangleTangentZ[FaceIndex]
3612 );
3613
3614 // Start building a list of faces adjacent to this face.
3615 AdjacentFaces.Reset();
3616 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
3617 {
3618 int32 ThisCornerIndex = WedgeOffset + CornerIndex;
3619 const TArray<int32>& DupVerts = OverlappingCorners.FindIfOverlapping(ThisCornerIndex);
3620 if (DupVerts.Num() == 0)
3621 {
3622 AdjacentFaces.AddUnique(ThisCornerIndex / 3); // I am a "dup" of myself
3623 }
3624 for (int32 k = 0; k < DupVerts.Num(); k++)
3625 {
3626 int32 PotentialTriangleIndex = DupVerts[k] / 3;
3627 //Do not add a triangle that was remove
3628 if (TriangleTangentZ.IsValidIndex(PotentialTriangleIndex))
3629 {
3630 bool bDegeneratedTriangles = TriangleTangentZ[FaceIndex].IsNearlyZero() || TriangleTangentZ[PotentialTriangleIndex].IsNearlyZero();
3631 //Do not add mirror triangle to the adjacentFaces. Also make sure adjacent triangle is in the same connected triangle patch. Accept connected degenerate triangle
3632 if ((bDegeneratedTriangles || !IsTriangleMirror(BuildData, TriangleTangentZ, FaceIndex, PotentialTriangleIndex)) && PatchIndex == FaceIndexToPatchIndex[PotentialTriangleIndex])
3633 {
3634 AdjacentFaces.AddUnique(PotentialTriangleIndex);
3635 }
3636 }
3637 }
3638 }
3639
3640 // We need to sort these here because the criteria for point equality is
3641 // exact, so we must ensure the exact same order for all dups.
3642 AdjacentFaces.Sort();
3643
3644 // Process adjacent faces
3645 for (int32 AdjacentFaceIndex = 0; AdjacentFaceIndex < AdjacentFaces.Num(); AdjacentFaceIndex++)
3646 {
3647 int32 OtherFaceIndex = AdjacentFaces[AdjacentFaceIndex];
3648 for (int32 OurCornerIndex = 0; OurCornerIndex < 3; OurCornerIndex++)
3649 {
3650 if (bCornerHasNormal[OurCornerIndex] && (bUseMikktSpace || bCornerHasTangents[OurCornerIndex]))
3651 continue;
3652
3653 FFanFace NewFanFace;
3654 int32 CommonIndexCount = 0;
3655
3656 // Check for vertices in common.
3657 if (FaceIndex == OtherFaceIndex)
3658 {
3659 CommonIndexCount = 3;
3660 NewFanFace.LinkedVertexIndex = OurCornerIndex;
3661 }
3662 else
3663 {
3664 // Check matching vertices against main vertex .
3665 for (int32 OtherCornerIndex = 0; OtherCornerIndex < 3; OtherCornerIndex++)
3666 {
3667 if (PointsEqual(
3668 CornerPositions[OurCornerIndex],
3669 BuildData->GetVertexPosition(OtherFaceIndex, OtherCornerIndex),
3670 BuildData->BuildOptions.OverlappingThresholds
3671 ))
3672 {
3673 CommonIndexCount++;
3674 NewFanFace.LinkedVertexIndex = OtherCornerIndex;
3675 }
3676 }
3677 }
3678
3679 // Add if connected by at least one point. Smoothing matches are considered later.
3680 if (CommonIndexCount > 0)
3681 {
3682 NewFanFace.FaceIndex = OtherFaceIndex;
3683 NewFanFace.bFilled = (OtherFaceIndex == FaceIndex); // Starter face for smoothing floodfill.
3684 NewFanFace.bBlendTangents = NewFanFace.bFilled;
3685 NewFanFace.bBlendNormals = NewFanFace.bFilled;
3686 RelevantFacesForCorner[OurCornerIndex].Add(NewFanFace);
3687 }
3688 }
3689 }
3690
3691 // Find true relevance of faces for a vertex normal by traversing
3692 // smoothing-group-compatible connected triangle fans around common vertices.
3693 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
3694 {
3695 if (bCornerHasNormal[CornerIndex] && (bUseMikktSpace || bCornerHasTangents[CornerIndex]))
3696 continue;
3697
3698 int32 NewConnections;
3699 do
3700 {
3701 NewConnections = 0;
3702 for (int32 OtherFaceIdx = 0; OtherFaceIdx < RelevantFacesForCorner[CornerIndex].Num(); OtherFaceIdx++)
3703 {
3704 FFanFace& OtherFace = RelevantFacesForCorner[CornerIndex][OtherFaceIdx];
3705 // The vertex' own face is initially the only face with bFilled == true.
3706 if (OtherFace.bFilled)
3707 {
3708 for (int32 NextFaceIndex = 0; NextFaceIndex < RelevantFacesForCorner[CornerIndex].Num(); NextFaceIndex++)
3709 {
3710 FFanFace& NextFace = RelevantFacesForCorner[CornerIndex][NextFaceIndex];
3711 if (!NextFace.bFilled) // && !NextFace.bBlendTangents)
3712 {
3713 if ((NextFaceIndex != OtherFaceIdx)
3714 && (BuildData->GetFaceSmoothingGroups(NextFace.FaceIndex) & BuildData->GetFaceSmoothingGroups(OtherFace.FaceIndex)))
3715 {
3716 int32 CommonVertices = 0;
3717 int32 CommonTangentVertices = 0;
3718 int32 CommonNormalVertices = 0;
3719 for (int32 OtherCornerIndex = 0; OtherCornerIndex < 3; OtherCornerIndex++)
3720 {
3721 for (int32 NextCornerIndex = 0; NextCornerIndex < 3; NextCornerIndex++)
3722 {
3723 int32 NextVertexIndex = BuildData->GetVertexIndex(NextFace.FaceIndex, NextCornerIndex);
3724 int32 OtherVertexIndex = BuildData->GetVertexIndex(OtherFace.FaceIndex, OtherCornerIndex);
3725 if (PointsEqual(
3726 BuildData->GetVertexPosition(NextFace.FaceIndex, NextCornerIndex),
3727 BuildData->GetVertexPosition(OtherFace.FaceIndex, OtherCornerIndex),
3728 BuildData->BuildOptions.OverlappingThresholds))
3729 {
3730 CommonVertices++;
3731 if (!bUseMikktSpace && UVsEqual(
3732 BuildData->GetVertexUV(NextFace.FaceIndex, NextCornerIndex, 0),
3733 BuildData->GetVertexUV(OtherFace.FaceIndex, OtherCornerIndex, 0),
3734 BuildData->BuildOptions.OverlappingThresholds))
3735 {
3736 CommonTangentVertices++;
3737 }
3738 if (bBlendOverlappingNormals
3739 || NextVertexIndex == OtherVertexIndex)
3740 {
3741 CommonNormalVertices++;
3742 }
3743 }
3744 }
3745 }
3746 // Flood fill faces with more than one common vertices which must be touching edges.
3747 if (CommonVertices > 1)
3748 {
3749 NextFace.bFilled = true;
3750 NextFace.bBlendNormals = (CommonNormalVertices > 1);
3751 NewConnections++;
3752 // Only blend tangents if there is no UV seam along the edge with this face.
3753 if (!bUseMikktSpace && OtherFace.bBlendTangents && CommonTangentVertices > 1)
3754 {
3755 float OtherDeterminant = FVector::Triple(
3756 TriangleTangentX[NextFace.FaceIndex],
3757 TriangleTangentY[NextFace.FaceIndex],
3758 TriangleTangentZ[NextFace.FaceIndex]
3759 );
3760 if ((Determinant * OtherDeterminant) > 0.0f)
3761 {
3762 NextFace.bBlendTangents = true;
3763 }
3764 }
3765 }
3766 }
3767 }
3768 }
3769 }
3770 }
3771 }
3772 while (NewConnections > 0);
3773 }
3774
3775 // Vertex normal construction.
3776 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
3777 {
3778 bool bUseProvidedNormal = bCornerHasNormal[CornerIndex];
3779 bool bUseProvidedTangents = !bUseMikktSpace && bCornerHasTangents[CornerIndex];
3780 if (bUseProvidedNormal)
3781 {
3782 CornerNormal[CornerIndex] = WedgeTangentZ[WedgeOffset + CornerIndex];
3783 }
3784 if (bUseProvidedTangents)
3785 {
3786 CornerTangentX[CornerIndex] = WedgeTangentX[WedgeOffset + CornerIndex];
3787 CornerTangentY[CornerIndex] = WedgeTangentY[WedgeOffset + CornerIndex];
3788 }
3789
3790 if (!bUseProvidedNormal || !bUseProvidedTangents)
3791 {
3792 for (int32 RelevantFaceIdx = 0; RelevantFaceIdx < RelevantFacesForCorner[CornerIndex].Num(); RelevantFaceIdx++)
3793 {
3794 FFanFace const& RelevantFace = RelevantFacesForCorner[CornerIndex][RelevantFaceIdx];
3795 if (RelevantFace.bFilled)
3796 {
3797 int32 OtherFaceIndex = RelevantFace.FaceIndex;
3798 float CornerWeight = 1.0f;
3799 if (bComputeWeightedNormals)
3800 {
3801 FVector OtherFacePoint[3] = { BuildData->GetVertexPosition(OtherFaceIndex, 0), BuildData->GetVertexPosition(OtherFaceIndex, 1), BuildData->GetVertexPosition(OtherFaceIndex, 2) };
3802 float OtherFaceArea = TriangleUtilities::ComputeTriangleArea(OtherFacePoint[0], OtherFacePoint[1], OtherFacePoint[2]);
3803 int32 OtherFaceCornerIndex = RelevantFace.LinkedVertexIndex;
3804 float OtherFaceAngle = TriangleUtilities::ComputeTriangleCornerAngle(OtherFacePoint[OtherFaceCornerIndex], OtherFacePoint[(OtherFaceCornerIndex + 1) % 3], OtherFacePoint[(OtherFaceCornerIndex + 2) % 3]);
3805 //Get the CornerWeight
3806 CornerWeight = OtherFaceArea * OtherFaceAngle;
3807 }
3808 if (!bUseProvidedTangents && RelevantFace.bBlendTangents)
3809 {
3810 CornerTangentX[CornerIndex] += CornerWeight * TriangleTangentX[OtherFaceIndex];
3811 CornerTangentY[CornerIndex] += CornerWeight * TriangleTangentY[OtherFaceIndex];
3812 }
3813 if (!bUseProvidedNormal && RelevantFace.bBlendNormals)
3814 {
3815 CornerNormal[CornerIndex] += CornerWeight * TriangleTangentZ[OtherFaceIndex];
3816 }
3817 }
3818 }
3819 if (!WedgeTangentX[WedgeOffset + CornerIndex].IsNearlyZero())
3820 {
3821 CornerTangentX[CornerIndex] = WedgeTangentX[WedgeOffset + CornerIndex];
3822 }
3823 if (!WedgeTangentY[WedgeOffset + CornerIndex].IsNearlyZero())
3824 {
3825 CornerTangentY[CornerIndex] = WedgeTangentY[WedgeOffset + CornerIndex];
3826 }
3827 if (!WedgeTangentZ[WedgeOffset + CornerIndex].IsNearlyZero())
3828 {
3829 CornerNormal[CornerIndex] = WedgeTangentZ[WedgeOffset + CornerIndex];
3830 }
3831 }
3832 }
3833
3834 // Normalization.
3835 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
3836 {
3837 CornerNormal[CornerIndex].Normalize();
3838 if (!bUseMikktSpace)
3839 {
3840 CornerTangentX[CornerIndex].Normalize();
3841 CornerTangentY[CornerIndex].Normalize();
3842
3843 // Gram-Schmidt orthogonalization
3844 CornerTangentY[CornerIndex] -= CornerTangentX[CornerIndex] * (CornerTangentX[CornerIndex] | CornerTangentY[CornerIndex]);
3845 CornerTangentY[CornerIndex].Normalize();
3846
3847 CornerTangentX[CornerIndex] -= CornerNormal[CornerIndex] * (CornerNormal[CornerIndex] | CornerTangentX[CornerIndex]);
3848 CornerTangentX[CornerIndex].Normalize();
3849 CornerTangentY[CornerIndex] -= CornerNormal[CornerIndex] * (CornerNormal[CornerIndex] | CornerTangentY[CornerIndex]);
3850 CornerTangentY[CornerIndex].Normalize();
3851 }
3852 }
3853
3854 auto VerifyTangentSpace = [&bIsZeroLengthNormalErrorMessageDisplayed, &BuildData](FVector& NormalizedVector)
3855 {
3856 if (NormalizedVector.IsNearlyZero() || NormalizedVector.ContainsNaN())
3857 {
3858 NormalizedVector = FVector::ZeroVector;
3859 //We also notify the log that we compute a zero length normals, so the user is aware of it
3860 if (!bIsZeroLengthNormalErrorMessageDisplayed)
3861 {
3862 bIsZeroLengthNormalErrorMessageDisplayed = true;
3863 // add warning message if available, do a log if not
3864 FText TextMessage = LOCTEXT("Skeletal_ComputeTangents_MikkTSpace_Warning_ZeroLengthNormal", "Skeletal ComputeTangents MikkTSpace function: Compute a zero length normal vector.");
3865 if (BuildData->OutWarningMessages)
3866 {
3867 BuildData->OutWarningMessages->Add(TextMessage);
3868 if (BuildData->OutWarningNames)
3869 {
3870 BuildData->OutWarningNames->Add(FFbxErrors::Generic_Mesh_TangentsComputeError);
3871 }
3872 }
3873 else
3874 {
3875 UE_LOG(LogSkeletalMesh, Display, TEXT("%s"), *(TextMessage.ToString()));
3876 }
3877 }
3878 }
3879 };
3880
3881 // Copy back to the mesh.
3882 for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++)
3883 {
3884 VerifyTangentSpace(CornerNormal[CornerIndex]);
3885 WedgeTangentZ[WedgeOffset + CornerIndex] = CornerNormal[CornerIndex];
3886
3887 if (!bUseMikktSpace)
3888 {
3889 VerifyTangentSpace(CornerTangentX[CornerIndex]);
3890 WedgeTangentX[WedgeOffset + CornerIndex] = CornerTangentX[CornerIndex];
3891
3892 VerifyTangentSpace(CornerTangentY[CornerIndex]);
3893 WedgeTangentY[WedgeOffset + CornerIndex] = CornerTangentY[CornerIndex];
3894 }
3895 }
3896 }
3897
3898 if (bUseMikktSpace)
3899 {
3900 // we can use mikktspace to calculate the tangents
3901 SMikkTSpaceContext MikkTContext;
3902 MikkTContext.m_pInterface = BuildData->GetMikkTInterface();
3903 MikkTContext.m_pUserData = BuildData->GetMikkTUserData();
3904 //MikkTContext.m_bIgnoreDegenerates = bIgnoreDegenerateTriangles;
3905
3906 genTangSpaceDefault(&MikkTContext);
3907 }
3908
3909 check(WedgeTangentX.Num() == NumWedges);
3910 check(WedgeTangentY.Num() == NumWedges);
3911 check(WedgeTangentZ.Num() == NumWedges);
3912 }
3913
3914 bool PrepareSourceMesh(IMeshBuildData* BuildData)
3915 {
3916 check(Stage == EStage::Uninit);
3917
3918 BeginSlowTask();
3919
3920 FOverlappingCorners& OverlappingCorners = *new FOverlappingCorners;
3921 LODOverlappingCorners.Add(&OverlappingCorners);
3922
3923 float ComparisonThreshold = THRESH_POINTS_ARE_SAME;//GetComparisonThreshold(LODBuildSettings[LODIndex]);
3924 int32 NumWedges = BuildData->GetNumWedges();
3925
3926 // Find overlapping corners to accelerate adjacency.
3927 Skeletal_FindOverlappingCorners(OverlappingCorners, BuildData, ComparisonThreshold);
3928
3929 // Figure out if we should recompute normals and tangents.
3930 bool bRecomputeNormals = BuildData->BuildOptions.bComputeNormals;
3931 bool bRecomputeTangents = BuildData->BuildOptions.bComputeTangents;
3932
3933 // Dump normals and tangents if we are recomputing them.
3934 if (bRecomputeTangents)
3935 {
3936 TArray<FVector>& TangentX = BuildData->GetTangentArray(0);
3937 TArray<FVector>& TangentY = BuildData->GetTangentArray(1);
3938
3939 TangentX.Empty(NumWedges);
3940 TangentX.AddZeroed(NumWedges);
3941 TangentY.Empty(NumWedges);
3942 TangentY.AddZeroed(NumWedges);
3943 }
3944 if (bRecomputeNormals)
3945 {
3946 TArray<FVector>& TangentZ = BuildData->GetTangentArray(2);
3947 TangentZ.Empty(NumWedges);
3948 TangentZ.AddZeroed(NumWedges);
3949 }
3950
3951 // Compute any missing tangents. MikkTSpace should be use only when the user want to recompute the normals or tangents otherwise should always fallback on builtin tangent
3952 Skeletal_ComputeTangents(BuildData, OverlappingCorners);
3953
3954 // At this point the mesh will have valid tangents.
3955 BuildData->ValidateTangentArraySize();
3956 check(LODOverlappingCorners.Num() == 1);
3957
3958 EndSlowTask();
3959
3960 Stage = EStage::Prepared;
3961 return true;
3962 }
3963
3964 bool GenerateSkeletalRenderMesh(IMeshBuildData* InBuildData)
3965 {
3966 check(Stage == EStage::Prepared);
3967
3968 SkeletalMeshBuildData& BuildData = *(SkeletalMeshBuildData*)InBuildData;
3969
3970 BeginSlowTask();
3971
3972 // Find wedge influences.
3973 TArray<int32> WedgeInfluenceIndices;
3974 TMap<uint32, uint32> VertexIndexToInfluenceIndexMap;
3975
3976 for (uint32 LookIdx = 0; LookIdx < (uint32)BuildData.Influences.Num(); LookIdx++)
3977 {
3978 // Order matters do not allow the map to overwrite an existing value.
3979 if (!VertexIndexToInfluenceIndexMap.Find(BuildData.Influences[LookIdx].VertIndex))
3980 {
3981 VertexIndexToInfluenceIndexMap.Add(BuildData.Influences[LookIdx].VertIndex, LookIdx);
3982 }
3983 }
3984
3985 for (int32 WedgeIndex = 0; WedgeIndex < BuildData.Wedges.Num(); WedgeIndex++)
3986 {
3987 uint32* InfluenceIndex = VertexIndexToInfluenceIndexMap.Find(BuildData.Wedges[WedgeIndex].iVertex);
3988
3989 if (InfluenceIndex)
3990 {
3991 WedgeInfluenceIndices.Add(*InfluenceIndex);
3992 }
3993 else
3994 {
3995 // we have missing influence vert, we weight to root
3996 WedgeInfluenceIndices.Add(0);
3997
3998 // add warning message
3999 if (BuildData.OutWarningMessages)
4000 {
4001 BuildData.OutWarningMessages->Add(FText::Format(FText::FromString("Missing influence on vert {0}. Weighting it to root."), FText::FromString(FString::FromInt(BuildData.Wedges[WedgeIndex].iVertex))));
4002 if (BuildData.OutWarningNames)
4003 {
4004 BuildData.OutWarningNames->Add(FFbxErrors::SkeletalMesh_VertMissingInfluences);
4005 }
4006 }
4007 }
4008 }
4009
4010 check(BuildData.Wedges.Num() == WedgeInfluenceIndices.Num());
4011
4012 TArray<FSkeletalMeshVertIndexAndZ> VertIndexAndZ;
4013 TArray<FSoftSkinBuildVertex> RawVertices;
4014
4015 VertIndexAndZ.Empty(BuildData.Points.Num());
4016 RawVertices.Reserve(BuildData.Points.Num());
4017
4018 for (int32 FaceIndex = 0; FaceIndex < BuildData.Faces.Num(); FaceIndex++)
4019 {
4020 // Only update the status progress bar if we are in the game thread and every thousand faces.
4021 // Updating status is extremely slow
4022 if (FaceIndex % 5000 == 0)
4023 {
4024 UpdateSlowTask(FaceIndex, BuildData.Faces.Num());
4025 }
4026
4027 const SkeletalMeshImportData::FMeshFace& Face = BuildData.Faces[FaceIndex];
4028
4029 for (int32 VertexIndex = 0; VertexIndex < 3; VertexIndex++)
4030 {
4031 FSoftSkinBuildVertex Vertex;
4032 const uint32 WedgeIndex = BuildData.GetWedgeIndex(FaceIndex, VertexIndex);
4033 const SkeletalMeshImportData::FMeshWedge& Wedge = BuildData.Wedges[WedgeIndex];
4034
4035 Vertex.Position = BuildData.GetVertexPosition(FaceIndex, VertexIndex);
4036
4037 FVector TangentX, TangentY, TangentZ;
4038 TangentX = BuildData.TangentX[WedgeIndex].GetSafeNormal();
4039 TangentY = BuildData.TangentY[WedgeIndex].GetSafeNormal();
4040 TangentZ = BuildData.TangentZ[WedgeIndex].GetSafeNormal();
4041
4042 // Normalize overridden tangents. Its possible for them to import un-normalized.
4043 TangentX.Normalize();
4044 TangentY.Normalize();
4045 TangentZ.Normalize();
4046
4047 Vertex.TangentX = TangentX;
4048 Vertex.TangentY = TangentY;
4049 Vertex.TangentZ = TangentZ;
4050
4051 FMemory::Memcpy(Vertex.UVs, Wedge.UVs, sizeof(FVector2D)*MAX_TEXCOORDS);
4052 Vertex.Color = Wedge.Color;
4053
4054 {
4055 // Count the influences.
4056 int32 InfIdx = WedgeInfluenceIndices[Face.iWedge[VertexIndex]];
4057 int32 LookIdx = InfIdx;
4058
4059 uint32 InfluenceCount = 0;
4060 while (BuildData.Influences.IsValidIndex(LookIdx) && (BuildData.Influences[LookIdx].VertIndex == Wedge.iVertex))
4061 {
4062 InfluenceCount++;
4063 LookIdx++;
4064 }
4065
4066 InfluenceCount = FMath::Min<uint32>(InfluenceCount, MAX_TOTAL_INFLUENCES);
4067 if (InfluenceCount > EXTRA_BONE_INFLUENCES && !FGPUBaseSkinVertexFactory::UseUnlimitedBoneInfluences(InfluenceCount))
4068 {
4069 InfluenceCount = EXTRA_BONE_INFLUENCES;
4070 UE_LOG(LogSkeletalMesh, Warning, TEXT("Skeletal mesh of %d bone influences requires unlimited bone influence mode on. Influence truncated to %d."), InfluenceCount, EXTRA_BONE_INFLUENCES);
4071 }
4072
4073 // Setup the vertex influences.
4074 Vertex.InfluenceBones[0] = 0;
4075 Vertex.InfluenceWeights[0] = 255;
4076 for (uint32 i = 1; i < MAX_TOTAL_INFLUENCES; i++)
4077 {
4078 Vertex.InfluenceBones[i] = 0;
4079 Vertex.InfluenceWeights[i] = 0;
4080 }
4081
4082 uint32 TotalInfluenceWeight = 0;
4083 for (uint32 i = 0; i < InfluenceCount; i++)
4084 {
4085 FBoneIndexType BoneIndex = (FBoneIndexType)BuildData.Influences[InfIdx + i].BoneIndex;
4086 if (BoneIndex >= BuildData.RefSkeleton.GetRawBoneNum())
4087 continue;
4088
4089 Vertex.InfluenceBones[i] = BoneIndex;
4090 Vertex.InfluenceWeights[i] = (uint8)(BuildData.Influences[InfIdx + i].Weight * 255.0f);
4091 TotalInfluenceWeight += Vertex.InfluenceWeights[i];
4092 }
4093 Vertex.InfluenceWeights[0] += 255 - TotalInfluenceWeight;
4094 }
4095
4096 // Add the vertex as well as its original index in the points array
4097 Vertex.PointWedgeIdx = Wedge.iVertex;
4098
4099 int32 RawIndex = RawVertices.Add(Vertex);
4100
4101 // Add an efficient way to find dupes of this vertex later for fast combining of vertices
4102 FSkeletalMeshVertIndexAndZ IAndZ;
4103 IAndZ.Index = RawIndex;
4104 IAndZ.Z = Vertex.Position.Z;
4105
4106 VertIndexAndZ.Add(IAndZ);
4107 }
4108 }
4109
4110 // Generate chunks and their vertices and indices
4111 SkeletalMeshTools::BuildSkeletalMeshChunks(BuildData.Faces, RawVertices, VertIndexAndZ, BuildData.BuildOptions.OverlappingThresholds, BuildData.Chunks, BuildData.bTooManyVerts);
4112
4113 //Get alternate skinning weights map to retrieve easily the data
4114 TMap<uint32, TArray<FBoneIndexType>> AlternateBoneIDs;
4115 AlternateBoneIDs.Reserve(BuildData.Points.Num());
4116 for (auto Kvp : BuildData.LODModel.SkinWeightProfiles)
4117 {
4118 FImportedSkinWeightProfileData& ImportedProfileData = Kvp.Value;
4119 if (ImportedProfileData.SourceModelInfluences.Num() > 0)
4120 {
4121 for (int32 InfluenceIndex = 0; InfluenceIndex < ImportedProfileData.SourceModelInfluences.Num(); ++InfluenceIndex)
4122 {
4123 const SkeletalMeshImportData::FVertInfluence& VertInfluence = ImportedProfileData.SourceModelInfluences[InfluenceIndex];
4124 if (VertInfluence.Weight > 0.0f)
4125 {
4126 TArray<FBoneIndexType>& BoneMap = AlternateBoneIDs.FindOrAdd(VertInfluence.VertIndex);
4127 BoneMap.AddUnique(VertInfluence.BoneIndex);
4128 }
4129 }
4130 }
4131 }
4132
4133 // Chunk vertices to satisfy the requested limit.
4134 const uint32 MaxGPUSkinBones = FGPUBaseSkinVertexFactory::GetMaxGPUSkinBones();
4135 check(MaxGPUSkinBones <= FGPUBaseSkinVertexFactory::GHardwareMaxGPUSkinBones);
4136 SkeletalMeshTools::ChunkSkinnedVertices(BuildData.Chunks, AlternateBoneIDs, MaxGPUSkinBones);
4137
4138 EndSlowTask();
4139
4140 Stage = EStage::GenerateRendering;
4141 return true;
4142 }
4143
4144 void BeginSlowTask()
4145 {
4146 if (IsInGameThread())
4147 {
4148 GWarn->BeginSlowTask(NSLOCTEXT("UnrealEd", "ProcessingSkeletalTriangles", "Processing Mesh Triangles"), true);
4149 }
4150 }
4151
4152 void UpdateSlowTask(int32 Numerator, int32 Denominator)
4153 {
4154 if (IsInGameThread())
4155 {
4156 GWarn->StatusUpdate(Numerator, Denominator, NSLOCTEXT("UnrealEd", "ProcessingSkeletalTriangles", "Processing Mesh Triangles"));
4157 }
4158 }
4159
4160 void EndSlowTask()
4161 {
4162 if (IsInGameThread())
4163 {
4164 GWarn->EndSlowTask();
4165 }
4166 }
4167
4168private:
4169 enum class EStage
4170 {
4171 Uninit,
4172 Prepared,
4173 GenerateRendering,
4174 };
4175
4176 TIndirectArray<FOverlappingCorners> LODOverlappingCorners;
4177 EStage Stage;
4178};
4179
4180bool FMeshUtilities::BuildSkeletalMesh(FSkeletalMeshLODModel& LODModel, const FReferenceSkeleton& RefSkeleton, const TArray<SkeletalMeshImportData::FVertInfluence>& Influences, const TArray<SkeletalMeshImportData::FMeshWedge>& Wedges, const TArray<SkeletalMeshImportData::FMeshFace>& Faces, const TArray<FVector>& Points, const TArray<int32>& PointToOriginalMap, const MeshBuildOptions& BuildOptions, TArray<FText> * OutWarningMessages, TArray<FName> * OutWarningNames)
4181{
4182#if WITH_EDITORONLY_DATA
4183
4184 auto UpdateOverlappingVertices = [](FSkeletalMeshLODModel& InLODModel)
4185 {
4186 // clear first
4187 for (int32 SectionIdx = 0; SectionIdx < InLODModel.Sections.Num(); SectionIdx++)
4188 {
4189 FSkelMeshSection& CurSection = InLODModel.Sections[SectionIdx];
4190 CurSection.OverlappingVertices.Reset();
4191 }
4192
4193 for (int32 SectionIdx = 0; SectionIdx < InLODModel.Sections.Num(); SectionIdx++)
4194 {
4195 FSkelMeshSection& CurSection = InLODModel.Sections[SectionIdx];
4196 const int32 NumSoftVertices = CurSection.SoftVertices.Num();
4197
4198 // Create a list of vertex Z/index pairs
4199 TArray<FIndexAndZ> VertIndexAndZ;
4200 VertIndexAndZ.Reserve(NumSoftVertices);
4201 for (int32 VertIndex = 0; VertIndex < NumSoftVertices; ++VertIndex)
4202 {
4203 FSoftSkinVertex& SrcVert = CurSection.SoftVertices[VertIndex];
4204 new(VertIndexAndZ)FIndexAndZ(VertIndex, SrcVert.Position);
4205 }
4206 VertIndexAndZ.Sort(FCompareIndexAndZ());
4207
4208 // Search for duplicates, quickly!
4209 for (int32 i = 0; i < VertIndexAndZ.Num(); ++i)
4210 {
4211 const uint32 SrcVertIndex = VertIndexAndZ[i].Index;
4212 const float Z = VertIndexAndZ[i].Z;
4213 FSoftSkinVertex& SrcVert = CurSection.SoftVertices[SrcVertIndex];
4214
4215 // only need to search forward, since we add pairs both ways
4216 for (int32 j = i + 1; j < VertIndexAndZ.Num(); ++j)
4217 {
4218 if (FMath::Abs(VertIndexAndZ[j].Z - Z) > THRESH_POINTS_ARE_SAME)
4219 break; // can't be any more dups
4220
4221 const uint32 IterVertIndex = VertIndexAndZ[j].Index;
4222 FSoftSkinVertex& IterVert = CurSection.SoftVertices[IterVertIndex];
4223 if (PointsEqual(SrcVert.Position, IterVert.Position))
4224 {
4225 // if so, we add to overlapping vert
4226 TArray<int32>& SrcValueArray = CurSection.OverlappingVertices.FindOrAdd(SrcVertIndex);
4227 SrcValueArray.Add(IterVertIndex);
4228
4229 TArray<int32>& IterValueArray = CurSection.OverlappingVertices.FindOrAdd(IterVertIndex);
4230 IterValueArray.Add(SrcVertIndex);
4231 }
4232 }
4233 }
4234 }
4235 };
4236
4237 SkeletalMeshBuildData BuildData(
4238 LODModel,
4239 RefSkeleton,
4240 Influences,
4241 Wedges,
4242 Faces,
4243 Points,
4244 PointToOriginalMap,
4245 BuildOptions,
4246 OutWarningMessages,
4247 OutWarningNames);
4248
4249 FSkeletalMeshUtilityBuilder Builder;
4250 if (!Builder.PrepareSourceMesh(&BuildData))
4251 {
4252 return false;
4253 }
4254
4255 if (!Builder.GenerateSkeletalRenderMesh(&BuildData))
4256 {
4257 return false;
4258 }
4259
4260 // Build the skeletal model from chunks.
4261 Builder.BeginSlowTask();
4262 BuildSkeletalModelFromChunks(BuildData.LODModel, BuildData.RefSkeleton, BuildData.Chunks, BuildData.PointToOriginalMap);
4263 UpdateOverlappingVertices(BuildData.LODModel);
4264 Builder.EndSlowTask();
4265
4266 // Only show these warnings if in the game thread. When importing morph targets, this function can run in another thread and these warnings dont prevent the mesh from importing
4267 if (IsInGameThread())
4268 {
4269 bool bHasBadSections = false;
4270 for (int32 SectionIndex = 0; SectionIndex < BuildData.LODModel.Sections.Num(); SectionIndex++)
4271 {
4272 FSkelMeshSection& Section = BuildData.LODModel.Sections[SectionIndex];
4273 bHasBadSections |= (Section.NumTriangles == 0);
4274
4275 // Log info about the section.
4276 UE_LOG(LogSkeletalMesh, Log, TEXT("Section %u: Material=%u, %u triangles"),
4277 SectionIndex,
4278 Section.MaterialIndex,
4279 Section.NumTriangles
4280 );
4281 }
4282 if (bHasBadSections)
4283 {
4284 FText BadSectionMessage(NSLOCTEXT("UnrealEd", "Error_SkeletalMeshHasBadSections", "Input mesh has a section with no triangles. This mesh may not render properly."));
4285 if (BuildData.OutWarningMessages)
4286 {
4287 BuildData.OutWarningMessages->Add(BadSectionMessage);
4288 if (BuildData.OutWarningNames)
4289 {
4290 BuildData.OutWarningNames->Add(FFbxErrors::SkeletalMesh_SectionWithNoTriangle);
4291 }
4292 }
4293 else
4294 {
4295 FMessageDialog::Open(EAppMsgType::Ok, BadSectionMessage);
4296 }
4297 }
4298
4299 if (BuildData.bTooManyVerts)
4300 {
4301 FText TooManyVertsMessage(NSLOCTEXT("UnrealEd", "Error_SkeletalMeshTooManyVertices", "Input mesh has too many vertices. The generated mesh will be corrupt! Consider adding extra materials to split up the source mesh into smaller chunks."));
4302
4303 if (BuildData.OutWarningMessages)
4304 {
4305 BuildData.OutWarningMessages->Add(TooManyVertsMessage);
4306 if (BuildData.OutWarningNames)
4307 {
4308 BuildData.OutWarningNames->Add(FFbxErrors::SkeletalMesh_TooManyVertices);
4309 }
4310 }
4311 else
4312 {
4313 FMessageDialog::Open(EAppMsgType::Ok, TooManyVertsMessage);
4314 }
4315 }
4316 }
4317
4318 return true;
4319#else
4320 if (OutWarningMessages)
4321 {
4322 OutWarningMessages->Add(FText::FromString(TEXT("Cannot call FMeshUtilities::BuildSkeletalMesh on a console!")));
4323 }
4324 else
4325 {
4326 UE_LOG(LogSkeletalMesh, Fatal, TEXT("Cannot call FMeshUtilities::BuildSkeletalMesh on a console!"));
4327 }
4328 return false;
4329#endif
4330}
4331
4332//The fail safe is there to avoid zeros in the tangents. Even if the fail safe prevent zero NTBs, a warning should be generate by the caller to let the artist know something went wrong
4333//Using a fail safe can lead to hard edge where its suppose to be smooth, it can also have some impact on the shading (lighting for tangentZ and normal map for tangentX and Y)
4334//Normally because we use the triangle data the tangent space is in a good direction and should give proper result.
4335void TangentFailSafe(const FVector &TriangleTangentX, const FVector &TriangleTangentY, const FVector &TriangleTangentZ
4336 , FVector &TangentX, FVector &TangentY, FVector &TangentZ)
4337{
4338 bool bTangentXZero = TangentX.IsNearlyZero() || TangentX.ContainsNaN();
4339 bool bTangentYZero = TangentY.IsNearlyZero() || TangentY.ContainsNaN();
4340 bool bTangentZZero = TangentZ.IsNearlyZero() || TangentZ.ContainsNaN();
4341
4342 if (!bTangentXZero && !bTangentYZero && !bTangentZZero)
4343 {
4344 //No need to fail safe if everything is different from zero
4345 return;
4346 }
4347 if (!bTangentZZero)
4348 {
4349 if (!bTangentXZero)
4350 {
4351 //Valid TangentZ and TangentX, we can recompute TangentY
4352 TangentY = FVector::CrossProduct(TangentZ, TangentX).GetSafeNormal();
4353 }
4354 else if (!bTangentYZero)
4355 {
4356 //Valid TangentZ and TangentY, we can recompute TangentX
4357 TangentX = FVector::CrossProduct(TangentY, TangentZ).GetSafeNormal();
4358 }
4359 else
4360 {
4361 //TangentX and Y are invalid, use the triangle data, can cause a hard edge
4362 TangentX = TriangleTangentX.GetSafeNormal();
4363 TangentY = TriangleTangentY.GetSafeNormal();
4364 }
4365 }
4366 else if (!bTangentXZero)
4367 {
4368 if (!bTangentYZero)
4369 {
4370 //Valid TangentX and TangentY, we can recompute TangentZ
4371 TangentZ = FVector::CrossProduct(TangentX, TangentY).GetSafeNormal();
4372 }
4373 else
4374 {
4375 //TangentY and Z are invalid, use the triangle data, can cause a hard edge
4376 TangentZ = TriangleTangentZ.GetSafeNormal();
4377 TangentY = TriangleTangentY.GetSafeNormal();
4378 }
4379 }
4380 else if (!bTangentYZero)
4381 {
4382 //TangentX and Z are invalid, use the triangle data, can cause a hard edge
4383 TangentX = TriangleTangentX.GetSafeNormal();
4384 TangentZ = TriangleTangentZ.GetSafeNormal();
4385 }
4386 else
4387 {
4388 //Everything is zero, use all triangle data, can cause a hard edge
4389 TangentX = TriangleTangentX.GetSafeNormal();
4390 TangentY = TriangleTangentY.GetSafeNormal();
4391 TangentZ = TriangleTangentZ.GetSafeNormal();
4392 }
4393
4394 bool bParaXY = FVector::Parallel(TangentX, TangentY);
4395 bool bParaYZ = FVector::Parallel(TangentY, TangentZ);
4396 bool bParaZX = FVector::Parallel(TangentZ, TangentX);
4397 if (bParaXY || bParaYZ || bParaZX)
4398 {
4399 //In case XY are parallel, use the Z(normal) if valid and not parallel to both X and Y to find the missing component
4400 if (bParaXY && !bParaZX)
4401 {
4402 TangentY = FVector::CrossProduct(TangentZ, TangentX).GetSafeNormal();
4403 }
4404 else if (bParaXY && !bParaYZ)
4405 {
4406 TangentX = FVector::CrossProduct(TangentY, TangentZ).GetSafeNormal();
4407 }
4408 else
4409 {
4410 //Degenerated value put something valid
4411 TangentX = FVector(1.0f, 0.0f, 0.0f);
4412 TangentY = FVector(0.0f, 1.0f, 0.0f);
4413 TangentZ = FVector(0.0f, 0.0f, 1.0f);
4414 }
4415 }
4416 else
4417 {
4418 //Ortho normalize the result
4419 TangentY -= TangentX * (TangentX | TangentY);
4420 TangentY.Normalize();
4421
4422 TangentX -= TangentZ * (TangentZ | TangentX);
4423 TangentY -= TangentZ * (TangentZ | TangentY);
4424
4425 TangentX.Normalize();
4426 TangentY.Normalize();
4427
4428 //If we still have some zero data (i.e. triangle data is degenerated)
4429 if (TangentZ.IsNearlyZero() || TangentZ.ContainsNaN()
4430 || TangentX.IsNearlyZero() || TangentX.ContainsNaN()
4431 || TangentY.IsNearlyZero() || TangentY.ContainsNaN())
4432 {
4433 //Since the triangle is degenerate this case can cause a hardedge, but will probably have no other impact since the triangle is degenerate (no visible surface)
4434 TangentX = FVector(1.0f, 0.0f, 0.0f);
4435 TangentY = FVector(0.0f, 1.0f, 0.0f);
4436 TangentZ = FVector(0.0f, 0.0f, 1.0f);
4437 }
4438 }
4439}
4440
4441//@TODO: The OutMessages has to be a struct that contains FText/FName, or make it Token and add that as error. Needs re-work. Temporary workaround for now.
4442bool FMeshUtilities::BuildSkeletalMesh_Legacy(FSkeletalMeshLODModel& LODModel
4443 , const FReferenceSkeleton& RefSkeleton
4444 , const TArray<SkeletalMeshImportData::FVertInfluence>& Influences
4445 , const TArray<SkeletalMeshImportData::FMeshWedge>& Wedges
4446 , const TArray<SkeletalMeshImportData::FMeshFace>& Faces
4447 , const TArray<FVector>& Points
4448 , const TArray<int32>& PointToOriginalMap
4449 , const FOverlappingThresholds& OverlappingThresholds
4450 , bool bComputeNormals
4451 , bool bComputeTangents
4452 , bool bComputeWeightedNormals
4453 , TArray<FText> * OutWarningMessages
4454 , TArray<FName> * OutWarningNames)
4455{
4456 bool bTooManyVerts = false;
4457
4458 check(PointToOriginalMap.Num() == Points.Num());
4459
4460 // Calculate face tangent vectors.
4461 TArray<FVector> FaceTangentX;
4462 TArray<FVector> FaceTangentY;
4463 FaceTangentX.AddUninitialized(Faces.Num());
4464 FaceTangentY.AddUninitialized(Faces.Num());
4465
4466 if (bComputeNormals || bComputeTangents)
4467 {
4468 for (int32 FaceIndex = 0; FaceIndex < Faces.Num(); FaceIndex++)
4469 {
4470 FVector P1 = Points[Wedges[Faces[FaceIndex].iWedge[0]].iVertex],
4471 P2 = Points[Wedges[Faces[FaceIndex].iWedge[1]].iVertex],
4472 P3 = Points[Wedges[Faces[FaceIndex].iWedge[2]].iVertex];
4473 FVector TriangleNormal = FPlane(P3, P2, P1);
4474 if (!TriangleNormal.IsNearlyZero(FLT_MIN))
4475 {
4476 FMatrix ParameterToLocal(
4477 FPlane(P2.X - P1.X, P2.Y - P1.Y, P2.Z - P1.Z, 0),
4478 FPlane(P3.X - P1.X, P3.Y - P1.Y, P3.Z - P1.Z, 0),
4479 FPlane(P1.X, P1.Y, P1.Z, 0),
4480 FPlane(0, 0, 0, 1)
4481 );
4482
4483 float U1 = Wedges[Faces[FaceIndex].iWedge[0]].UVs[0].X,
4484 U2 = Wedges[Faces[FaceIndex].iWedge[1]].UVs[0].X,
4485 U3 = Wedges[Faces[FaceIndex].iWedge[2]].UVs[0].X,
4486 V1 = Wedges[Faces[FaceIndex].iWedge[0]].UVs[0].Y,
4487 V2 = Wedges[Faces[FaceIndex].iWedge[1]].UVs[0].Y,
4488 V3 = Wedges[Faces[FaceIndex].iWedge[2]].UVs[0].Y;
4489
4490 FMatrix ParameterToTexture(
4491 FPlane(U2 - U1, V2 - V1, 0, 0),
4492 FPlane(U3 - U1, V3 - V1, 0, 0),
4493 FPlane(U1, V1, 1, 0),
4494 FPlane(0, 0, 0, 1)
4495 );
4496
4497 FMatrix TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal;
4498 FVector TangentX = TextureToLocal.TransformVector(FVector(1, 0, 0)).GetSafeNormal(),
4499 TangentY = TextureToLocal.TransformVector(FVector(0, 1, 0)).GetSafeNormal(),
4500 TangentZ;
4501
4502 TangentX = TangentX - TriangleNormal * (TangentX | TriangleNormal);
4503 TangentY = TangentY - TriangleNormal * (TangentY | TriangleNormal);
4504
4505 FaceTangentX[FaceIndex] = TangentX.GetSafeNormal();
4506 FaceTangentY[FaceIndex] = TangentY.GetSafeNormal();
4507 }
4508 else
4509 {
4510 FaceTangentX[FaceIndex] = FVector::ZeroVector;
4511 FaceTangentY[FaceIndex] = FVector::ZeroVector;
4512 }
4513 }
4514 }
4515
4516 TArray<int32> WedgeInfluenceIndices;
4517
4518 // Find wedge influences.
4519 TMap<uint32, uint32> VertexIndexToInfluenceIndexMap;
4520
4521 for (uint32 LookIdx = 0; LookIdx < (uint32)Influences.Num(); LookIdx++)
4522 {
4523 // Order matters do not allow the map to overwrite an existing value.
4524 if (!VertexIndexToInfluenceIndexMap.Find(Influences[LookIdx].VertIndex))
4525 {
4526 VertexIndexToInfluenceIndexMap.Add(Influences[LookIdx].VertIndex, LookIdx);
4527 }
4528 }
4529
4530 for (int32 WedgeIndex = 0; WedgeIndex < Wedges.Num(); WedgeIndex++)
4531 {
4532 uint32* InfluenceIndex = VertexIndexToInfluenceIndexMap.Find(Wedges[WedgeIndex].iVertex);
4533
4534 if (InfluenceIndex)
4535 {
4536 WedgeInfluenceIndices.Add(*InfluenceIndex);
4537 }
4538 else
4539 {
4540 // we have missing influence vert, we weight to root
4541 WedgeInfluenceIndices.Add(0);
4542
4543 // add warning message
4544 if (OutWarningMessages)
4545 {
4546 OutWarningMessages->Add(FText::Format(FText::FromString("Missing influence on vert {0}. Weighting it to root."), FText::FromString(FString::FromInt(Wedges[WedgeIndex].iVertex))));
4547 if (OutWarningNames)
4548 {
4549 OutWarningNames->Add(FFbxErrors::SkeletalMesh_VertMissingInfluences);
4550 }
4551 }
4552 }
4553 }
4554
4555 check(Wedges.Num() == WedgeInfluenceIndices.Num());
4556
4557 // Calculate smooth wedge tangent vectors.
4558
4559 if (IsInGameThread())
4560 {
4561 // Only update status if in the game thread. When importing morph targets, this function can run in another thread
4562 GWarn->BeginSlowTask(NSLOCTEXT("UnrealEd", "ProcessingSkeletalTriangles", "Processing Mesh Triangles"), true);
4563 }
4564
4565
4566 // To accelerate generation of adjacency, we'll create a table that maps each vertex index
4567 // to its overlapping vertices, and a table that maps a vertex to the its influenced faces
4568 TMultiMap<int32, int32> Vert2Duplicates;
4569 TMultiMap<int32, int32> Vert2Faces;
4570 TArray<FSkeletalMeshVertIndexAndZ> VertIndexAndZ;
4571 {
4572 // Create a list of vertex Z/index pairs
4573 VertIndexAndZ.Empty(Points.Num());
4574 for (int32 i = 0; i < Points.Num(); i++)
4575 {
4576 FSkeletalMeshVertIndexAndZ iandz;
4577 iandz.Index = i;
4578 iandz.Z = Points[i].Z;
4579 VertIndexAndZ.Add(iandz);
4580 }
4581
4582 // Sorting function for vertex Z/index pairs
4583 struct FCompareFSkeletalMeshVertIndexAndZ
4584 {
4585 FORCEINLINE bool operator()(const FSkeletalMeshVertIndexAndZ& A, const FSkeletalMeshVertIndexAndZ& B) const
4586 {
4587 return A.Z < B.Z;
4588 }
4589 };
4590
4591 // Sort the vertices by z value
4592 VertIndexAndZ.Sort(FCompareFSkeletalMeshVertIndexAndZ());
4593
4594 // Search for duplicates, quickly!
4595 for (int32 i = 0; i < VertIndexAndZ.Num(); i++)
4596 {
4597 // only need to search forward, since we add pairs both ways
4598 for (int32 j = i + 1; j < VertIndexAndZ.Num(); j++)
4599 {
4600 if (FMath::Abs(VertIndexAndZ[j].Z - VertIndexAndZ[i].Z) > OverlappingThresholds.ThresholdPosition)
4601 {
4602 // our list is sorted, so there can't be any more dupes
4603 break;
4604 }
4605
4606 // check to see if the points are really overlapping
4607 if (PointsEqual(
4608 Points[VertIndexAndZ[i].Index],
4609 Points[VertIndexAndZ[j].Index], OverlappingThresholds))
4610 {
4611 Vert2Duplicates.Add(VertIndexAndZ[i].Index, VertIndexAndZ[j].Index);
4612 Vert2Duplicates.Add(VertIndexAndZ[j].Index, VertIndexAndZ[i].Index);
4613 }
4614 }
4615 }
4616
4617 // we are done with this
4618 VertIndexAndZ.Reset();
4619
4620 // now create a map from vert indices to faces
4621 for (int32 FaceIndex = 0; FaceIndex < Faces.Num(); FaceIndex++)
4622 {
4623 const SkeletalMeshImportData::FMeshFace& Face = Faces[FaceIndex];
4624 for (int32 VertexIndex = 0; VertexIndex < 3; VertexIndex++)
4625 {
4626 Vert2Faces.AddUnique(Wedges[Face.iWedge[VertexIndex]].iVertex, FaceIndex);
4627 }
4628 }
4629 }
4630
4631 TArray<FSkinnedMeshChunk*> Chunks;
4632 TArray<int32> AdjacentFaces;
4633 TArray<int32> DupVerts;
4634 TArray<int32> DupFaces;
4635
4636 // List of raw calculated vertices that will be merged later
4637 TArray<FSoftSkinBuildVertex> RawVertices;
4638 RawVertices.Reserve(Points.Num());
4639
4640 int32 NTBErrorCount = 0;
4641 // Create a list of vertex Z/index pairs
4642
4643 for (int32 FaceIndex = 0; FaceIndex < Faces.Num(); FaceIndex++)
4644 {
4645 // Only update the status progress bar if we are in the gamethread and every thousand faces.
4646 // Updating status is extremely slow
4647 if (FaceIndex % 5000 == 0 && IsInGameThread())
4648 {
4649 // Only update status if in the game thread. When importing morph targets, this function can run in another thread
4650 GWarn->StatusUpdate(FaceIndex, Faces.Num(), NSLOCTEXT("UnrealEd", "ProcessingSkeletalTriangles", "Processing Mesh Triangles"));
4651 }
4652
4653 const SkeletalMeshImportData::FMeshFace& Face = Faces[FaceIndex];
4654
4655 FVector VertexTangentX[3],
4656 VertexTangentY[3],
4657 VertexTangentZ[3];
4658
4659 if (bComputeNormals || bComputeTangents)
4660 {
4661 for (int32 VertexIndex = 0; VertexIndex < 3; VertexIndex++)
4662 {
4663 VertexTangentX[VertexIndex] = FVector::ZeroVector;
4664 VertexTangentY[VertexIndex] = FVector::ZeroVector;
4665 VertexTangentZ[VertexIndex] = FVector::ZeroVector;
4666 }
4667
4668 FVector TriangleNormal = FPlane(
4669 Points[Wedges[Face.iWedge[2]].iVertex],
4670 Points[Wedges[Face.iWedge[1]].iVertex],
4671 Points[Wedges[Face.iWedge[0]].iVertex]
4672 );
4673 float Determinant = FVector::Triple(FaceTangentX[FaceIndex], FaceTangentY[FaceIndex], TriangleNormal);
4674
4675 // Start building a list of faces adjacent to this triangle
4676 AdjacentFaces.Reset();
4677 for (int32 VertexIndex = 0; VertexIndex < 3; VertexIndex++)
4678 {
4679 int32 vert = Wedges[Face.iWedge[VertexIndex]].iVertex;
4680 DupVerts.Reset();
4681 Vert2Duplicates.MultiFind(vert, DupVerts);
4682 DupVerts.Add(vert); // I am a "dupe" of myself
4683 for (int32 k = 0; k < DupVerts.Num(); k++)
4684 {
4685 DupFaces.Reset();
4686 Vert2Faces.MultiFind(DupVerts[k], DupFaces);
4687 for (int32 l = 0; l < DupFaces.Num(); l++)
4688 {
4689 AdjacentFaces.AddUnique(DupFaces[l]);
4690 }
4691 }
4692 }
4693
4694 FVector FacePoint[3] = { Points[Wedges[Face.iWedge[0]].iVertex], Points[Wedges[Face.iWedge[1]].iVertex], Points[Wedges[Face.iWedge[2]].iVertex] };
4695 // Process adjacent faces
4696 for (int32 AdjacentFaceIndex = 0; AdjacentFaceIndex < AdjacentFaces.Num(); AdjacentFaceIndex++)
4697 {
4698 int32 OtherFaceIndex = AdjacentFaces[AdjacentFaceIndex];
4699 const SkeletalMeshImportData::FMeshFace& OtherFace = Faces[OtherFaceIndex];
4700
4701 FVector OtherFacePoint[3] = { Points[Wedges[OtherFace.iWedge[0]].iVertex], Points[Wedges[OtherFace.iWedge[1]].iVertex], Points[Wedges[OtherFace.iWedge[2]].iVertex] };
4702 float OtherFaceArea = !bComputeWeightedNormals ? 1.0f : TriangleUtilities::ComputeTriangleArea(OtherFacePoint[0], OtherFacePoint[1], OtherFacePoint[2]);
4703 FVector OtherTriangleNormal = FPlane(
4704 OtherFacePoint[2],
4705 OtherFacePoint[1],
4706 OtherFacePoint[0]
4707 );
4708 float OtherFaceDeterminant = FVector::Triple(FaceTangentX[OtherFaceIndex], FaceTangentY[OtherFaceIndex], OtherTriangleNormal);
4709
4710 for (int32 VertexIndex = 0; VertexIndex < 3; VertexIndex++)
4711 {
4712 for (int32 OtherVertexIndex = 0; OtherVertexIndex < 3; OtherVertexIndex++)
4713 {
4714 if (PointsEqual(
4715 OtherFacePoint[OtherVertexIndex],
4716 FacePoint[VertexIndex],
4717 OverlappingThresholds
4718 ))
4719 {
4720 //Compute the angle
4721 float OtherFaceAngle = !bComputeWeightedNormals ? 1.0f : TriangleUtilities::ComputeTriangleCornerAngle(OtherFacePoint[OtherVertexIndex], OtherFacePoint[(OtherVertexIndex + 1) % 3], OtherFacePoint[(OtherVertexIndex + 2) % 3]);
4722
4723 float CornerWeight = (OtherFaceArea * OtherFaceAngle);
4724
4725 if (Determinant * OtherFaceDeterminant > 0.0f && SkeletalMeshTools::SkeletalMesh_UVsEqual(Wedges[OtherFace.iWedge[OtherVertexIndex]], Wedges[Face.iWedge[VertexIndex]], OverlappingThresholds))
4726 {
4727 VertexTangentX[VertexIndex] += CornerWeight *FaceTangentX[OtherFaceIndex];
4728 VertexTangentY[VertexIndex] += CornerWeight *FaceTangentY[OtherFaceIndex];
4729 }
4730
4731 // Only contribute 'normal' if the vertices are truly one and the same to obey hard "smoothing" edges baked into
4732 // the mesh by vertex duplication
4733 if (Wedges[OtherFace.iWedge[OtherVertexIndex]].iVertex == Wedges[Face.iWedge[VertexIndex]].iVertex)
4734 {
4735 VertexTangentZ[VertexIndex] += CornerWeight *OtherTriangleNormal;
4736 }
4737 }
4738 }
4739 }
4740 }
4741 }
4742
4743 for (int32 VertexIndex = 0; VertexIndex < 3; VertexIndex++)
4744 {
4745 FSoftSkinBuildVertex Vertex;
4746
4747 Vertex.Position = Points[Wedges[Face.iWedge[VertexIndex]].iVertex];
4748
4749 FVector TangentX, TangentY, TangentZ;
4750
4751 if (bComputeNormals || bComputeTangents)
4752 {
4753 TangentX = VertexTangentX[VertexIndex].GetSafeNormal();
4754 TangentY = VertexTangentY[VertexIndex].GetSafeNormal();
4755
4756 if (bComputeNormals)
4757 {
4758 TangentZ = VertexTangentZ[VertexIndex].GetSafeNormal();
4759 }
4760 else
4761 {
4762 TangentZ = Face.TangentZ[VertexIndex];
4763 }
4764
4765 TangentY -= TangentX * (TangentX | TangentY);
4766 TangentY.Normalize();
4767
4768 TangentX -= TangentZ * (TangentZ | TangentX);
4769 TangentY -= TangentZ * (TangentZ | TangentY);
4770
4771 TangentX.Normalize();
4772 TangentY.Normalize();
4773 }
4774 else
4775 {
4776 TangentX = Face.TangentX[VertexIndex];
4777 TangentY = Face.TangentY[VertexIndex];
4778 TangentZ = Face.TangentZ[VertexIndex];
4779
4780 // Normalize overridden tangents. Its possible for them to import un-normalized.
4781 TangentX.Normalize();
4782 TangentY.Normalize();
4783 TangentZ.Normalize();
4784 }
4785
4786 //FAIL safe, avoid zero tangents
4787 bool bTangentXZero = TangentX.IsNearlyZero() || TangentX.ContainsNaN();
4788 bool bTangentYZero = TangentY.IsNearlyZero() || TangentY.ContainsNaN();
4789 bool bTangentZZero = TangentZ.IsNearlyZero() || TangentZ.ContainsNaN();
4790 if (bTangentXZero || bTangentYZero || bTangentZZero)
4791 {
4792 NTBErrorCount++;
4793 FVector TriangleTangentZ = FPlane(
4794 Points[Wedges[Face.iWedge[2]].iVertex],
4795 Points[Wedges[Face.iWedge[1]].iVertex],
4796 Points[Wedges[Face.iWedge[0]].iVertex]
4797 );
4798 FVector TriangleTangentX = FaceTangentX[FaceIndex];
4799 FVector TriangleTangentY = FaceTangentY[FaceIndex];
4800 TangentFailSafe(TriangleTangentX, TriangleTangentY, TriangleTangentZ, TangentX, TangentY, TangentZ);
4801 }
4802
4803 Vertex.TangentX = TangentX;
4804 Vertex.TangentY = TangentY;
4805 Vertex.TangentZ = TangentZ;
4806
4807 FMemory::Memcpy(Vertex.UVs, Wedges[Face.iWedge[VertexIndex]].UVs, sizeof(FVector2D)*MAX_TEXCOORDS);
4808 Vertex.Color = Wedges[Face.iWedge[VertexIndex]].Color;
4809
4810 {
4811 // Count the influences.
4812
4813 int32 InfIdx = WedgeInfluenceIndices[Face.iWedge[VertexIndex]];
4814 int32 LookIdx = InfIdx;
4815
4816 uint32 InfluenceCount = 0;
4817 while (Influences.IsValidIndex(LookIdx) && (Influences[LookIdx].VertIndex == Wedges[Face.iWedge[VertexIndex]].iVertex))
4818 {
4819 InfluenceCount++;
4820 LookIdx++;
4821 }
4822 InfluenceCount = FMath::Min<uint32>(InfluenceCount, MAX_TOTAL_INFLUENCES);
4823
4824 // Setup the vertex influences.
4825
4826 Vertex.InfluenceBones[0] = 0;
4827 Vertex.InfluenceWeights[0] = 255;
4828 for (uint32 i = 1; i < MAX_TOTAL_INFLUENCES; i++)
4829 {
4830 Vertex.InfluenceBones[i] = 0;
4831 Vertex.InfluenceWeights[i] = 0;
4832 }
4833
4834 uint32 TotalInfluenceWeight = 0;
4835 for (uint32 i = 0; i < InfluenceCount; i++)
4836 {
4837 FBoneIndexType BoneIndex = (FBoneIndexType)Influences[InfIdx + i].BoneIndex;
4838 if (BoneIndex >= RefSkeleton.GetRawBoneNum())
4839 continue;
4840
4841 Vertex.InfluenceBones[i] = BoneIndex;
4842 Vertex.InfluenceWeights[i] = (uint8)(Influences[InfIdx + i].Weight * 255.0f);
4843 TotalInfluenceWeight += Vertex.InfluenceWeights[i];
4844 }
4845 Vertex.InfluenceWeights[0] += 255 - TotalInfluenceWeight;
4846 }
4847
4848 // Add the vertex as well as its original index in the points array
4849 Vertex.PointWedgeIdx = Wedges[Face.iWedge[VertexIndex]].iVertex;
4850
4851 int32 RawIndex = RawVertices.Add(Vertex);
4852
4853 // Add an efficient way to find dupes of this vertex later for fast combining of vertices
4854 FSkeletalMeshVertIndexAndZ IAndZ;
4855 IAndZ.Index = RawIndex;
4856 IAndZ.Z = Vertex.Position.Z;
4857
4858 VertIndexAndZ.Add(IAndZ);
4859 }
4860 }
4861
4862 if (NTBErrorCount > 0 && OutWarningMessages)
4863 {
4864 OutWarningMessages->Add(FText::FromString(TEXT("SkeletalMesh compute tangents [built in]: Build result data contain 0 or NAN tangent value. Bad tangent value will impact shading.")));
4865 if (OutWarningNames)
4866 {
4867 OutWarningNames->Add(FFbxErrors::Generic_Mesh_TangentsComputeError);
4868 }
4869 }
4870
4871 // Generate chunks and their vertices and indices
4872 SkeletalMeshTools::BuildSkeletalMeshChunks(Faces, RawVertices, VertIndexAndZ, OverlappingThresholds, Chunks, bTooManyVerts);
4873
4874 //Get alternate skinning weights map to retrieve easily the data
4875 TMap<uint32, TArray<FBoneIndexType>> AlternateBoneIDs;
4876 AlternateBoneIDs.Reserve(Points.Num());
4877 for (auto Kvp : LODModel.SkinWeightProfiles)
4878 {
4879 FImportedSkinWeightProfileData& ImportedProfileData = Kvp.Value;
4880 if (ImportedProfileData.SourceModelInfluences.Num() > 0)
4881 {
4882 for (int32 InfluenceIndex = 0; InfluenceIndex < ImportedProfileData.SourceModelInfluences.Num(); ++InfluenceIndex)
4883 {
4884 const SkeletalMeshImportData::FVertInfluence& VertInfluence = ImportedProfileData.SourceModelInfluences[InfluenceIndex];
4885 if (VertInfluence.Weight > 0.0f)
4886 {
4887 TArray<FBoneIndexType>& BoneMap = AlternateBoneIDs.FindOrAdd(VertInfluence.VertIndex);
4888 BoneMap.AddUnique(VertInfluence.BoneIndex);
4889 }
4890 }
4891 }
4892 }
4893
4894 // Chunk vertices to satisfy the requested limit.
4895 const uint32 MaxGPUSkinBones = FGPUBaseSkinVertexFactory::GetMaxGPUSkinBones();
4896 check(MaxGPUSkinBones <= FGPUBaseSkinVertexFactory::GHardwareMaxGPUSkinBones);
4897 SkeletalMeshTools::ChunkSkinnedVertices(Chunks, AlternateBoneIDs, MaxGPUSkinBones);
4898
4899 // Build the skeletal model from chunks.
4900 BuildSkeletalModelFromChunks(LODModel, RefSkeleton, Chunks, PointToOriginalMap);
4901
4902 if (IsInGameThread())
4903 {
4904 // Only update status if in the game thread. When importing morph targets, this function can run in another thread
4905 GWarn->EndSlowTask();
4906 }
4907
4908 // Only show these warnings if in the game thread. When importing morph targets, this function can run in another thread and these warnings dont prevent the mesh from importing
4909 if (IsInGameThread())
4910 {
4911 bool bHasBadSections = false;
4912 for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
4913 {
4914 FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
4915 bHasBadSections |= (Section.NumTriangles == 0);
4916
4917 // Log info about the section.
4918 UE_LOG(LogSkeletalMesh, Log, TEXT("Section %u: Material=%u, %u triangles"),
4919 SectionIndex,
4920 Section.MaterialIndex,
4921 Section.NumTriangles
4922 );
4923 }
4924 if (bHasBadSections)
4925 {
4926 FText BadSectionMessage(NSLOCTEXT("UnrealEd", "Error_SkeletalMeshHasBadSections", "Input mesh has a section with no triangles. This mesh may not render properly."));
4927 if (OutWarningMessages)
4928 {
4929 OutWarningMessages->Add(BadSectionMessage);
4930 if (OutWarningNames)
4931 {
4932 OutWarningNames->Add(FFbxErrors::SkeletalMesh_SectionWithNoTriangle);
4933 }
4934 }
4935 else
4936 {
4937 FMessageDialog::Open(EAppMsgType::Ok, BadSectionMessage);
4938 }
4939 }
4940
4941 if (bTooManyVerts)
4942 {
4943 FText TooManyVertsMessage(NSLOCTEXT("UnrealEd", "Error_SkeletalMeshTooManyVertices", "Input mesh has too many vertices. The generated mesh will be corrupt! Consider adding extra materials to split up the source mesh into smaller chunks."));
4944
4945 if (OutWarningMessages)
4946 {
4947 OutWarningMessages->Add(TooManyVertsMessage);
4948 if (OutWarningNames)
4949 {
4950 OutWarningNames->Add(FFbxErrors::SkeletalMesh_TooManyVertices);
4951 }
4952 }
4953 else
4954 {
4955 FMessageDialog::Open(EAppMsgType::Ok, TooManyVertsMessage);
4956 }
4957 }
4958 }
4959
4960 return true;
4961}
4962
4963static bool NonOpaqueMaterialPredicate(UStaticMeshComponent* InMesh)
4964{
4965 TArray<UMaterialInterface*> OutMaterials;
4966 InMesh->GetUsedMaterials(OutMaterials);
4967 for (auto Material : OutMaterials)
4968 {
4969 if (Material == nullptr || Material->GetBlendMode() != BLEND_Opaque)
4970 {
4971 return true;
4972 }
4973 }
4974
4975 return false;
4976}
4977
4978void FMeshUtilities::RecomputeTangentsAndNormalsForRawMesh(bool bRecomputeTangents, bool bRecomputeNormals, const FMeshBuildSettings& InBuildSettings, FRawMesh &OutRawMesh) const
4979{
4980 // Compute any missing tangents.
4981 if (bRecomputeNormals || bRecomputeTangents)
4982 {
4983 float ComparisonThreshold = InBuildSettings.bRemoveDegenerates ? THRESH_POINTS_ARE_SAME : 0.0f;
4984 FOverlappingCorners OverlappingCorners;
4985 FindOverlappingCorners(OverlappingCorners, OutRawMesh, ComparisonThreshold);
4986
4987 RecomputeTangentsAndNormalsForRawMesh( bRecomputeTangents, bRecomputeNormals, InBuildSettings, OverlappingCorners, OutRawMesh );
4988 }
4989}
4990
4991void FMeshUtilities::RecomputeTangentsAndNormalsForRawMesh(bool bRecomputeTangents, bool bRecomputeNormals, const FMeshBuildSettings& InBuildSettings, const FOverlappingCorners& InOverlappingCorners, FRawMesh &OutRawMesh) const
4992{
4993 const int32 NumWedges = OutRawMesh.WedgeIndices.Num();
4994
4995 // Dump normals and tangents if we are recomputing them.
4996 if (bRecomputeTangents)
4997 {
4998 OutRawMesh.WedgeTangentX.Empty(NumWedges);
4999 OutRawMesh.WedgeTangentX.AddZeroed(NumWedges);
5000 OutRawMesh.WedgeTangentY.Empty(NumWedges);
5001 OutRawMesh.WedgeTangentY.AddZeroed(NumWedges);
5002 }
5003
5004 if (bRecomputeNormals)
5005 {
5006 OutRawMesh.WedgeTangentZ.Empty(NumWedges);
5007 OutRawMesh.WedgeTangentZ.AddZeroed(NumWedges);
5008 }
5009
5010 // Compute any missing tangents.
5011 if (bRecomputeNormals || bRecomputeTangents)
5012 {
5013 // Static meshes always blend normals of overlapping corners.
5014 uint32 TangentOptions = ETangentOptions::BlendOverlappingNormals;
5015 if (InBuildSettings.bRemoveDegenerates)
5016 {
5017 // If removing degenerate triangles, ignore them when computing tangents.
5018 TangentOptions |= ETangentOptions::IgnoreDegenerateTriangles;
5019 }
5020
5021 if (InBuildSettings.bUseMikkTSpace)
5022 {
5023 ComputeTangents_MikkTSpace(OutRawMesh, InOverlappingCorners, TangentOptions);
5024 }
5025 else
5026 {
5027 ComputeTangents(OutRawMesh, InOverlappingCorners, TangentOptions);
5028 }
5029 }
5030
5031 // At this point the mesh will have valid tangents.
5032 check(OutRawMesh.WedgeTangentX.Num() == NumWedges);
5033 check(OutRawMesh.WedgeTangentY.Num() == NumWedges);
5034 check(OutRawMesh.WedgeTangentZ.Num() == NumWedges);
5035}
5036
5037void FMeshUtilities::ExtractMeshDataForGeometryCache(FRawMesh& RawMesh, const FMeshBuildSettings& BuildSettings, TArray<FStaticMeshBuildVertex>& OutVertices, TArray<TArray<uint32> >& OutPerSectionIndices, int32 ImportVersion)
5038{
5039 int32 NumWedges = RawMesh.WedgeIndices.Num();
5040
5041 // Figure out if we should recompute normals and tangents. By default generated LODs should not recompute normals
5042 bool bRecomputeNormals = (BuildSettings.bRecomputeNormals) || RawMesh.WedgeTangentZ.Num() == 0;
5043 bool bRecomputeTangents = (BuildSettings.bRecomputeTangents) || RawMesh.WedgeTangentX.Num() == 0 || RawMesh.WedgeTangentY.Num() == 0;
5044
5045 // Dump normals and tangents if we are recomputing them.
5046 if (bRecomputeTangents)
5047 {
5048 RawMesh.WedgeTangentX.Empty(NumWedges);
5049 RawMesh.WedgeTangentX.AddZeroed(NumWedges);
5050 RawMesh.WedgeTangentY.Empty(NumWedges);
5051 RawMesh.WedgeTangentY.AddZeroed(NumWedges);
5052 }
5053
5054 if (bRecomputeNormals)
5055 {
5056 RawMesh.WedgeTangentZ.Empty(NumWedges);
5057 RawMesh.WedgeTangentZ.AddZeroed(NumWedges);
5058 }
5059
5060 // Compute any missing tangents.
5061 FOverlappingCorners OverlappingCorners;
5062 if (bRecomputeNormals || bRecomputeTangents)
5063 {
5064 float ComparisonThreshold = GetComparisonThreshold(BuildSettings);
5065 FindOverlappingCorners(OverlappingCorners, RawMesh, ComparisonThreshold);
5066
5067 // Static meshes always blend normals of overlapping corners.
5068 uint32 TangentOptions = ETangentOptions::BlendOverlappingNormals;
5069 if (BuildSettings.bRemoveDegenerates)
5070 {
5071 // If removing degenerate triangles, ignore them when computing tangents.
5072 TangentOptions |= ETangentOptions::IgnoreDegenerateTriangles;
5073 }
5074 if (BuildSettings.bUseMikkTSpace)
5075 {
5076 ComputeTangents_MikkTSpace(RawMesh, OverlappingCorners, TangentOptions);
5077 }
5078 else
5079 {
5080 ComputeTangents(RawMesh, OverlappingCorners, TangentOptions);
5081 }
5082 }
5083
5084 // At this point the mesh will have valid tangents.
5085 check(RawMesh.WedgeTangentX.Num() == NumWedges);
5086 check(RawMesh.WedgeTangentY.Num() == NumWedges);
5087 check(RawMesh.WedgeTangentZ.Num() == NumWedges);
5088
5089 TArray<int32> OutWedgeMap;
5090
5091 int32 MaxMaterialIndex = 1;
5092 for (int32 FaceIndex = 0; FaceIndex < RawMesh.FaceMaterialIndices.Num(); FaceIndex++)
5093 {
5094 MaxMaterialIndex = FMath::Max<int32>(RawMesh.FaceMaterialIndices[FaceIndex], MaxMaterialIndex);
5095 }
5096
5097 TMap<uint32, uint32> MaterialToSectionMapping;
5098 for (int32 i = 0; i <= MaxMaterialIndex; ++i)
5099 {
5100 OutPerSectionIndices.Push(TArray<uint32>());
5101 MaterialToSectionMapping.Add(i, i);
5102 }
5103
5104 BuildStaticMeshVertexAndIndexBuffers(OutVertices, OutPerSectionIndices, OutWedgeMap, RawMesh, OverlappingCorners, MaterialToSectionMapping, KINDA_SMALL_NUMBER, BuildSettings.BuildScale3D, ImportVersion);
5105
5106 if (RawMesh.WedgeIndices.Num() < 100000 * 3)
5107 {
5108 CacheOptimizeVertexAndIndexBuffer(OutVertices, OutPerSectionIndices, OutWedgeMap);
5109 check(OutWedgeMap.Num() == RawMesh.WedgeIndices.Num());
5110 }
5111}
5112
5113/*------------------------------------------------------------------------------
5114Mesh merging
5115------------------------------------------------------------------------------*/
5116
5117void FMeshUtilities::CalculateTextureCoordinateBoundsForSkeletalMesh(const FSkeletalMeshLODModel& LODModel, TArray<FBox2D>& OutBounds) const
5118{
5119 TArray<FSoftSkinVertex> Vertices;
5120 LODModel.GetVertices(Vertices);
5121
5122 const uint32 SectionCount = (uint32)LODModel.NumNonClothingSections();
5123
5124 check(OutBounds.Num() != 0);
5125
5126 for (uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
5127 {
5128 const FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
5129 const uint32 FirstIndex = Section.BaseIndex;
5130 const uint32 LastIndex = FirstIndex + Section.NumTriangles * 3;
5131 const int32 MaterialIndex = Section.MaterialIndex;
5132
5133 if (OutBounds.Num() <= MaterialIndex)
5134 {
5135 OutBounds.SetNumZeroed(MaterialIndex + 1);
5136 }
5137
5138 for (uint32 Index = FirstIndex; Index < LastIndex; ++Index)
5139 {
5140 uint32 VertexIndex = LODModel.IndexBuffer[Index];
5141 FSoftSkinVertex& Vertex = Vertices[VertexIndex];
5142
5143 FVector2D TexCoord = Vertex.UVs[0];
5144 OutBounds[MaterialIndex] += TexCoord;
5145 }
5146 }
5147}
5148
5149bool FMeshUtilities::RemoveBonesFromMesh(USkeletalMesh* SkeletalMesh, int32 LODIndex, const TArray<FName>* BoneNamesToRemove) const
5150{
5151 IMeshBoneReductionModule& MeshBoneReductionModule = FModuleManager::Get().LoadModuleChecked<IMeshBoneReductionModule>("MeshBoneReduction");
5152 IMeshBoneReduction * MeshBoneReductionInterface = MeshBoneReductionModule.GetMeshBoneReductionInterface();
5153
5154 return MeshBoneReductionInterface->ReduceBoneCounts(SkeletalMesh, LODIndex, BoneNamesToRemove);
5155}
5156
5157class FMeshSimplifcationSettingsCustomization : public IDetailCustomization
5158{
5159public:
5160 static TSharedRef<IDetailCustomization> MakeInstance()
5161 {
5162 return MakeShareable( new FMeshSimplifcationSettingsCustomization );
5163 }
5164
5165 virtual void CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) override
5166 {
5167 MeshReductionModuleProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UMeshSimplificationSettings, MeshReductionModuleName));
5168
5169 IDetailCategoryBuilder& Category = DetailBuilder.EditCategory(TEXT("General"));
5170
5171 IDetailPropertyRow& PropertyRow = Category.AddProperty(MeshReductionModuleProperty);
5172
5173 FDetailWidgetRow& WidgetRow = PropertyRow.CustomWidget();
5174 WidgetRow.NameContent()
5175 [
5176 MeshReductionModuleProperty->CreatePropertyNameWidget()
5177 ];
5178
5179 WidgetRow.ValueContent()
5180 .MaxDesiredWidth(0)
5181 [
5182 SNew(SComboButton)
5183 .OnGetMenuContent(this, &FMeshSimplifcationSettingsCustomization::GenerateMeshSimplifierMenu)
5184 .ContentPadding(FMargin(2.0f, 2.0f))
5185 .ButtonContent()
5186 [
5187 SNew(STextBlock)
5188 .Font(IDetailLayoutBuilder::GetDetailFont())
5189 .Text(this, &FMeshSimplifcationSettingsCustomization::GetCurrentMeshSimplifierName)
5190 ]
5191 ];
5192 }
5193
5194private:
5195 FText GetCurrentMeshSimplifierName() const
5196 {
5197 if(MeshReductionModuleProperty->IsValidHandle())
5198 {
5199 FText Name;
5200 MeshReductionModuleProperty->GetValueAsDisplayText(Name);
5201
5202 return Name;
5203 }
5204 else
5205 {
5206 return LOCTEXT("AutomaticMeshReductionPlugin", "Automatic");
5207 }
5208 }
5209
5210 TSharedRef<SWidget> GenerateMeshSimplifierMenu() const
5211 {
5212 FMenuBuilder MenuBuilder(true, nullptr);
5213
5214 TArray<FName> ModuleNames;
5215 FModuleManager::Get().FindModules(TEXT("*MeshReduction"), ModuleNames);
5216
5217 if(ModuleNames.Num() > 0)
5218 {
5219 for(FName ModuleName : ModuleNames)
5220 {
5221 IMeshReductionModule& Module = FModuleManager::LoadModuleChecked<IMeshReductionModule>(ModuleName);
5222
5223 IMeshReduction* StaticMeshReductionInterface = Module.GetStaticMeshReductionInterface();
5224 // Only include options that support static mesh reduction.
5225 if (StaticMeshReductionInterface)
5226 {
5227 FUIAction UIAction;
5228 UIAction.ExecuteAction.BindSP(const_cast<FMeshSimplifcationSettingsCustomization*>(this), &FMeshSimplifcationSettingsCustomization::OnMeshSimplificationModuleChosen, ModuleName);
5229 UIAction.GetActionCheckState.BindSP(const_cast<FMeshSimplifcationSettingsCustomization*>(this), &FMeshSimplifcationSettingsCustomization::IsMeshSimplificationModuleChosen, ModuleName);
5230
5231 MenuBuilder.AddMenuEntry(FText::FromName(ModuleName), FText::GetEmpty(), FSlateIcon(), UIAction, NAME_None, EUserInterfaceActionType::RadioButton);
5232 }
5233 }
5234
5235 MenuBuilder.AddMenuSeparator();
5236 }
5237
5238
5239 FUIAction OpenMarketplaceAction;
5240 OpenMarketplaceAction.ExecuteAction.BindSP(const_cast<FMeshSimplifcationSettingsCustomization*>(this), &FMeshSimplifcationSettingsCustomization::OnFindReductionPluginsClicked);
5241 FSlateIcon Icon = FSlateIcon(FEditorStyle::Get().GetStyleSetName(), "LevelEditor.OpenMarketplace.Menu");
5242 MenuBuilder.AddMenuEntry( LOCTEXT("FindMoreReductionPluginsLink", "Search the Marketplace"), LOCTEXT("FindMoreReductionPluginsLink_Tooltip", "Opens the Marketplace to find more mesh reduction plugins"), Icon, OpenMarketplaceAction);
5243 return MenuBuilder.MakeWidget();
5244 }
5245
5246 void OnMeshSimplificationModuleChosen(FName ModuleName)
5247 {
5248 if(MeshReductionModuleProperty->IsValidHandle())
5249 {
5250 MeshReductionModuleProperty->SetValue(ModuleName);
5251 }
5252 }
5253
5254 ECheckBoxState IsMeshSimplificationModuleChosen(FName ModuleName)
5255 {
5256 if(MeshReductionModuleProperty->IsValidHandle())
5257 {
5258 FName CurrentModuleName;
5259 MeshReductionModuleProperty->GetValue(CurrentModuleName);
5260 return CurrentModuleName == ModuleName ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
5261 }
5262
5263 return ECheckBoxState::Unchecked;
5264 }
5265
5266 void OnFindReductionPluginsClicked()
5267 {
5268 FString URL;
5269 FUnrealEdMisc::Get().GetURL(TEXT("MeshSimplificationPluginsURL"), URL);
5270
5271 FUnrealEdMisc::Get().OpenMarketplace(URL);
5272 }
5273private:
5274 TSharedPtr<IPropertyHandle> MeshReductionModuleProperty;
5275};
5276
5277class FSkeletalMeshSimplificationSettingsCustomization : public IDetailCustomization
5278{
5279public:
5280 static TSharedRef<IDetailCustomization> MakeInstance()
5281 {
5282 return MakeShareable(new FSkeletalMeshSimplificationSettingsCustomization);
5283 }
5284
5285 virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override
5286 {
5287 SkeletalMeshReductionModuleProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(USkeletalMeshSimplificationSettings, SkeletalMeshReductionModuleName));
5288
5289 IDetailCategoryBuilder& Category = DetailBuilder.EditCategory(TEXT("General"));
5290
5291 IDetailPropertyRow& PropertyRow = Category.AddProperty(SkeletalMeshReductionModuleProperty);
5292
5293 FDetailWidgetRow& WidgetRow = PropertyRow.CustomWidget();
5294 WidgetRow.NameContent()
5295 [
5296 SkeletalMeshReductionModuleProperty->CreatePropertyNameWidget()
5297 ];
5298
5299 WidgetRow.ValueContent()
5300 .MaxDesiredWidth(0)
5301 [
5302 SNew(SComboButton)
5303 .OnGetMenuContent(this, &FSkeletalMeshSimplificationSettingsCustomization::GenerateSkeletalMeshSimplifierMenu)
5304 .ContentPadding(FMargin(2.0f, 2.0f))
5305 .ButtonContent()
5306 [
5307 SNew(STextBlock)
5308 .Font(IDetailLayoutBuilder::GetDetailFont())
5309 .Text(this, &FSkeletalMeshSimplificationSettingsCustomization::GetCurrentSkeletalMeshSimplifierName)
5310 ]
5311 ];
5312 }
5313
5314private:
5315 FText GetCurrentSkeletalMeshSimplifierName() const
5316 {
5317 if (SkeletalMeshReductionModuleProperty->IsValidHandle())
5318 {
5319 FText Name;
5320 SkeletalMeshReductionModuleProperty->GetValueAsDisplayText(Name);
5321
5322 return Name;
5323 }
5324 else
5325 {
5326 return LOCTEXT("AutomaticSkeletalMeshReductionPlugin", "Automatic");
5327 }
5328 }
5329
5330 TSharedRef<SWidget> GenerateSkeletalMeshSimplifierMenu() const
5331 {
5332 FMenuBuilder MenuBuilder(true, nullptr);
5333
5334 TArray<FName> ModuleNames;
5335 FModuleManager::Get().FindModules(TEXT("*MeshReduction"), ModuleNames);
5336
5337 if (ModuleNames.Num() > 0)
5338 {
5339 for (FName ModuleName : ModuleNames)
5340 {
5341 IMeshReductionModule& Module = FModuleManager::LoadModuleChecked<IMeshReductionModule>(ModuleName);
5342
5343 IMeshReduction* SkeletalMeshReductionInterface = Module.GetSkeletalMeshReductionInterface();
5344 // Only include options that support skeletal simplification.
5345 if (SkeletalMeshReductionInterface)
5346 {
5347 FUIAction UIAction;
5348 UIAction.ExecuteAction.BindSP(const_cast<FSkeletalMeshSimplificationSettingsCustomization*>(this), &FSkeletalMeshSimplificationSettingsCustomization::OnSkeletalMeshSimplificationModuleChosen, ModuleName);
5349 UIAction.GetActionCheckState.BindSP(const_cast<FSkeletalMeshSimplificationSettingsCustomization*>(this), &FSkeletalMeshSimplificationSettingsCustomization::IsSkeletalMeshSimplificationModuleChosen, ModuleName);
5350
5351 MenuBuilder.AddMenuEntry(FText::FromName(ModuleName), FText::GetEmpty(), FSlateIcon(), UIAction, NAME_None, EUserInterfaceActionType::RadioButton);
5352 }
5353 }
5354
5355 MenuBuilder.AddMenuSeparator();
5356 }
5357
5358
5359 FUIAction OpenMarketplaceAction;
5360 OpenMarketplaceAction.ExecuteAction.BindSP(const_cast<FSkeletalMeshSimplificationSettingsCustomization*>(this), &FSkeletalMeshSimplificationSettingsCustomization::OnFindReductionPluginsClicked);
5361 FSlateIcon Icon = FSlateIcon(FEditorStyle::Get().GetStyleSetName(), "LevelEditor.OpenMarketplace.Menu");
5362 MenuBuilder.AddMenuEntry(LOCTEXT("FindMoreReductionPluginsLink", "Search the Marketplace"), LOCTEXT("FindMoreReductionPluginsLink_Tooltip", "Opens the Marketplace to find more mesh reduction plugins"), Icon, OpenMarketplaceAction);
5363 return MenuBuilder.MakeWidget();
5364 }
5365
5366 void OnSkeletalMeshSimplificationModuleChosen(FName ModuleName)
5367 {
5368 if (SkeletalMeshReductionModuleProperty->IsValidHandle())
5369 {
5370 SkeletalMeshReductionModuleProperty->SetValue(ModuleName);
5371 }
5372 }
5373
5374 ECheckBoxState IsSkeletalMeshSimplificationModuleChosen(FName ModuleName)
5375 {
5376 if (SkeletalMeshReductionModuleProperty->IsValidHandle())
5377 {
5378 FName CurrentModuleName;
5379 SkeletalMeshReductionModuleProperty->GetValue(CurrentModuleName);
5380 return CurrentModuleName == ModuleName ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
5381 }
5382
5383 return ECheckBoxState::Unchecked;
5384 }
5385
5386 void OnFindReductionPluginsClicked()
5387 {
5388 FString URL;
5389 FUnrealEdMisc::Get().GetURL(TEXT("MeshSimplificationPluginsURL"), URL);
5390
5391 FUnrealEdMisc::Get().OpenMarketplace(URL);
5392 }
5393private:
5394 TSharedPtr<IPropertyHandle> SkeletalMeshReductionModuleProperty;
5395};
5396
5397class FProxyLODMeshSimplificationSettingsCustomization : public IDetailCustomization
5398{
5399public:
5400 static TSharedRef<IDetailCustomization> MakeInstance()
5401 {
5402 return MakeShareable(new FProxyLODMeshSimplificationSettingsCustomization);
5403 }
5404
5405 virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override
5406 {
5407 ProxyLODMeshReductionModuleProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UProxyLODMeshSimplificationSettings, ProxyLODMeshReductionModuleName));
5408
5409 IDetailCategoryBuilder& Category = DetailBuilder.EditCategory(TEXT("General"));
5410
5411 IDetailPropertyRow& PropertyRow = Category.AddProperty(ProxyLODMeshReductionModuleProperty);
5412
5413 FDetailWidgetRow& WidgetRow = PropertyRow.CustomWidget();
5414 WidgetRow.NameContent()
5415 [
5416 ProxyLODMeshReductionModuleProperty->CreatePropertyNameWidget()
5417 ];
5418
5419 WidgetRow.ValueContent()
5420 .MaxDesiredWidth(0)
5421 [
5422 SNew(SComboButton)
5423 .OnGetMenuContent(this, &FProxyLODMeshSimplificationSettingsCustomization::GenerateProxyLODMeshSimplifierMenu)
5424 .ContentPadding(FMargin(2.0f, 2.0f))
5425 .ButtonContent()
5426 [
5427 SNew(STextBlock)
5428 .Font(IDetailLayoutBuilder::GetDetailFont())
5429 .Text(this, &FProxyLODMeshSimplificationSettingsCustomization::GetCurrentProxyLODMeshSimplifierName)
5430 ]
5431 ];
5432 }
5433
5434private:
5435 FText GetCurrentProxyLODMeshSimplifierName() const
5436 {
5437 if (ProxyLODMeshReductionModuleProperty->IsValidHandle())
5438 {
5439 FText Name;
5440 ProxyLODMeshReductionModuleProperty->GetValueAsDisplayText(Name);
5441
5442 return Name;
5443 }
5444 else
5445 {
5446 return LOCTEXT("AutomaticProxyLODMeshReductionPlugin", "Automatic");
5447 }
5448 }
5449
5450 TSharedRef<SWidget> GenerateProxyLODMeshSimplifierMenu() const
5451 {
5452 FMenuBuilder MenuBuilder(true, nullptr);
5453
5454 TArray<FName> ModuleNames;
5455 FModuleManager::Get().FindModules(TEXT("*MeshReduction"), ModuleNames);
5456
5457 if (ModuleNames.Num() > 0)
5458 {
5459 for (FName ModuleName : ModuleNames)
5460 {
5461 IMeshReductionModule& Module = FModuleManager::LoadModuleChecked<IMeshReductionModule>(ModuleName);
5462
5463 IMeshMerging* MeshMergingInterface = Module.GetMeshMergingInterface();
5464 // Only include options that support mesh mergine.
5465 if (MeshMergingInterface)
5466 {
5467 FUIAction UIAction;
5468 UIAction.ExecuteAction.BindSP(const_cast<FProxyLODMeshSimplificationSettingsCustomization*>(this), &FProxyLODMeshSimplificationSettingsCustomization::OnProxyLODMeshSimplificationModuleChosen, ModuleName);
5469 UIAction.GetActionCheckState.BindSP(const_cast<FProxyLODMeshSimplificationSettingsCustomization*>(this), &FProxyLODMeshSimplificationSettingsCustomization::IsProxyLODMeshSimplificationModuleChosen, ModuleName);
5470
5471 MenuBuilder.AddMenuEntry(FText::FromName(ModuleName), FText::GetEmpty(), FSlateIcon(), UIAction, NAME_None, EUserInterfaceActionType::RadioButton);
5472 }
5473 }
5474
5475 MenuBuilder.AddMenuSeparator();
5476 }
5477
5478
5479 FUIAction OpenMarketplaceAction;
5480 OpenMarketplaceAction.ExecuteAction.BindSP(const_cast<FProxyLODMeshSimplificationSettingsCustomization*>(this), &FProxyLODMeshSimplificationSettingsCustomization::OnFindReductionPluginsClicked);
5481 FSlateIcon Icon = FSlateIcon(FEditorStyle::Get().GetStyleSetName(), "LevelEditor.OpenMarketplace.Menu");
5482 MenuBuilder.AddMenuEntry(LOCTEXT("FindMoreReductionPluginsLink", "Search the Marketplace"), LOCTEXT("FindMoreReductionPluginsLink_Tooltip", "Opens the Marketplace to find more mesh reduction plugins"), Icon, OpenMarketplaceAction);
5483 return MenuBuilder.MakeWidget();
5484 }
5485
5486 void OnProxyLODMeshSimplificationModuleChosen(FName ModuleName)
5487 {
5488 if (ProxyLODMeshReductionModuleProperty->IsValidHandle())
5489 {
5490 ProxyLODMeshReductionModuleProperty->SetValue(ModuleName);
5491 }
5492 }
5493
5494 ECheckBoxState IsProxyLODMeshSimplificationModuleChosen(FName ModuleName)
5495 {
5496 if (ProxyLODMeshReductionModuleProperty->IsValidHandle())
5497 {
5498 FName CurrentModuleName;
5499 ProxyLODMeshReductionModuleProperty->GetValue(CurrentModuleName);
5500 return CurrentModuleName == ModuleName ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
5501 }
5502
5503 return ECheckBoxState::Unchecked;
5504 }
5505
5506 void OnFindReductionPluginsClicked()
5507 {
5508 FString URL;
5509 FUnrealEdMisc::Get().GetURL(TEXT("MeshSimplificationPluginsURL"), URL);
5510
5511 FUnrealEdMisc::Get().OpenMarketplace(URL);
5512 }
5513private:
5514 TSharedPtr<IPropertyHandle> ProxyLODMeshReductionModuleProperty;
5515};
5516
5517/*------------------------------------------------------------------------------
5518Module initialization / teardown.
5519------------------------------------------------------------------------------*/
5520
5521void FMeshUtilities::StartupModule()
5522{
5523 FModuleManager::Get().LoadModule("MaterialBaking");
5524 FModuleManager::Get().LoadModule(TEXT("MeshMergeUtilities"));
5525
5526 FPropertyEditorModule& PropertyEditorModule = FModuleManager::Get().LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
5527
5528 PropertyEditorModule.RegisterCustomClassLayout("MeshSimplificationSettings", FOnGetDetailCustomizationInstance::CreateStatic(&FMeshSimplifcationSettingsCustomization::MakeInstance));
5529 PropertyEditorModule.RegisterCustomClassLayout("SkeletalMeshSimplificationSettings", FOnGetDetailCustomizationInstance::CreateStatic(&FSkeletalMeshSimplificationSettingsCustomization::MakeInstance));
5530 PropertyEditorModule.RegisterCustomClassLayout("ProxyLODMeshSimplificationSettings", FOnGetDetailCustomizationInstance::CreateStatic(&FProxyLODMeshSimplificationSettingsCustomization::MakeInstance));
5531
5532 static const TConsoleVariableData<int32>* CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.TriangleOrderOptimization"));
5533
5534 bDisableTriangleOrderOptimization = (CVar->GetValueOnGameThread() == 2);
5535
5536 bUsingNvTriStrip = !bDisableTriangleOrderOptimization && (CVar->GetValueOnGameThread() == 0);
5537
5538 IMeshReductionManagerModule& Module = FModuleManager::Get().LoadModuleChecked<IMeshReductionManagerModule>("MeshReductionInterface");
5539 IMeshReduction* StaticMeshReduction = Module.GetStaticMeshReductionInterface();
5540
5541
5542 // Construct and cache the version string for the mesh utilities module.
5543 VersionString = FString::Printf(
5544 TEXT("%s%s%s"),
5545 MESH_UTILITIES_VER,
5546 StaticMeshReduction ? *StaticMeshReduction->GetVersionString() : TEXT(""),
5547 bUsingNvTriStrip ? TEXT("_NvTriStrip") : TEXT("")
5548 );
5549
5550 // hook up level editor extension for skeletal mesh conversion
5551 ModuleLoadedDelegateHandle = FModuleManager::Get().OnModulesChanged().AddLambda([this](FName InModuleName, EModuleChangeReason InChangeReason)
5552 {
5553 if (InChangeReason == EModuleChangeReason::ModuleLoaded)
5554 {
5555 if (InModuleName == "LevelEditor")
5556 {
5557 AddLevelViewportMenuExtender();
5558 }
5559 else if (InModuleName == "AnimationBlueprintEditor")
5560 {
5561 AddAnimationBlueprintEditorToolbarExtender();
5562 }
5563 else if (InModuleName == "AnimationEditor")
5564 {
5565 AddAnimationEditorToolbarExtender();
5566 }
5567 else if (InModuleName == "SkeletonEditor")
5568 {
5569 AddSkeletonEditorToolbarExtender();
5570 }
5571 }
5572 });
5573
5574 UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FMeshUtilities::RegisterMenus));
5575}
5576
5577void FMeshUtilities::ShutdownModule()
5578{
5579 UToolMenus::UnRegisterStartupCallback(this);
5580 UToolMenus::UnregisterOwner(this);
5581
5582 static const FName PropertyEditorModuleName("PropertyEditor");
5583 if(FModuleManager::Get().IsModuleLoaded(PropertyEditorModuleName))
5584 {
5585 FPropertyEditorModule& PropertyEditorModule = FModuleManager::Get().GetModuleChecked<FPropertyEditorModule>(PropertyEditorModuleName);
5586
5587 PropertyEditorModule.UnregisterCustomClassLayout("MeshSimplificationSettings");
5588 PropertyEditorModule.UnregisterCustomClassLayout("SkeletalMeshSimplificationSettings");
5589 PropertyEditorModule.UnregisterCustomClassLayout("ProxyLODMeshSimplificationSettings");
5590 }
5591
5592 RemoveLevelViewportMenuExtender();
5593 RemoveAnimationBlueprintEditorToolbarExtender();
5594 RemoveAnimationEditorToolbarExtender();
5595 RemoveSkeletonEditorToolbarExtender();
5596 FModuleManager::Get().OnModulesChanged().Remove(ModuleLoadedDelegateHandle);
5597 VersionString.Empty();
5598}
5599
5600void FMeshUtilities::RegisterMenus()
5601{
5602 FToolMenuOwnerScoped OwnerScoped(this);
5603
5604 static auto AddMakeStaticMeshToolbarButton = [this](FToolMenuSection& InSection, const FToolMenuExecuteAction& InAction)
5605 {
5606 InSection.AddEntry(FToolMenuEntry::InitToolBarButton(
5607 "MakeStaticMesh",
5608 InAction,
5609 LOCTEXT("MakeStaticMesh", "Make Static Mesh"),
5610 LOCTEXT("MakeStaticMeshTooltip", "Make a new static mesh out of the preview's current pose."),
5611 FSlateIcon("EditorStyle", "Persona.ConvertToStaticMesh")
5612 ));
5613 };
5614
5615 {
5616 UToolMenu* Toolbar = UToolMenus::Get()->ExtendMenu("AssetEditor.SkeletalMeshEditor.ToolBar");
5617 FToolMenuSection& Section = Toolbar->FindOrAddSection("SkeletalMesh");
5618 AddMakeStaticMeshToolbarButton(Section, FToolMenuExecuteAction::CreateLambda([this](const FToolMenuContext& InMenuContext)
5619 {
5620 USkeletalMeshToolMenuContext* Context = InMenuContext.FindContext<USkeletalMeshToolMenuContext>();
5621 if (Context && Context->SkeletalMeshEditor.IsValid())
5622 {
5623 if (UMeshComponent* MeshComponent = Context->SkeletalMeshEditor.Pin()->GetPersonaToolkit()->GetPreviewMeshComponent())
5624 {
5625 ConvertMeshesToStaticMesh(TArray<UMeshComponent*>({ MeshComponent }), MeshComponent->GetComponentToWorld());
5626 }
5627 }
5628 }));
5629 }
5630}
5631
5632bool FMeshUtilities::GenerateUniqueUVsForSkeletalMesh(const FSkeletalMeshLODModel& LODModel, int32 TextureResolution, TArray<FVector2D>& OutTexCoords) const
5633{
5634 // Get easy to use SkeletalMesh data
5635 TArray<FSoftSkinVertex> Vertices;
5636 LODModel.GetVertices(Vertices);
5637
5638 int32 NumCorners = LODModel.IndexBuffer.Num();
5639
5640 // Generate FRawMesh from FSkeletalMeshLODModel
5641 FRawMesh TempMesh;
5642 TempMesh.WedgeIndices.AddUninitialized(NumCorners);
5643 TempMesh.WedgeTexCoords[0].AddUninitialized(NumCorners);
5644 TempMesh.VertexPositions.AddUninitialized(NumCorners);
5645
5646 // Prepare vertex to wedge map
5647 // PrevCorner[i] points to previous corner which shares the same wedge
5648 TArray<int32> LastWedgeCorner;
5649 LastWedgeCorner.AddUninitialized(Vertices.Num());
5650 TArray<int32> PrevCorner;
5651 PrevCorner.AddUninitialized(NumCorners);
5652 for (int32 Index = 0; Index < Vertices.Num(); Index++)
5653 {
5654 LastWedgeCorner[Index] = -1;
5655 }
5656
5657 for (int32 Index = 0; Index < NumCorners; Index++)
5658 {
5659 // Copy static vertex data
5660 int32 VertexIndex = LODModel.IndexBuffer[Index];
5661 FSoftSkinVertex& Vertex = Vertices[VertexIndex];
5662 TempMesh.WedgeIndices[Index] = Index; // rudimental data, not really used by FLayoutUV - but array size matters
5663 TempMesh.WedgeTexCoords[0][Index] = Vertex.UVs[0];
5664 TempMesh.VertexPositions[Index] = Vertex.Position;
5665 // Link all corners belonging to a single wedge into list
5666 int32 PrevCornerIndex = LastWedgeCorner[VertexIndex];
5667 LastWedgeCorner[VertexIndex] = Index;
5668 PrevCorner[Index] = PrevCornerIndex;
5669 }
5670
5671 // Build overlapping corners map
5672 FOverlappingCorners OverlappingCorners;
5673 OverlappingCorners.Init(NumCorners);
5674 for (int32 Index = 0; Index < NumCorners; Index++)
5675 {
5676 int VertexIndex = LODModel.IndexBuffer[Index];
5677 for (int32 CornerIndex = LastWedgeCorner[VertexIndex]; CornerIndex >= 0; CornerIndex = PrevCorner[CornerIndex])
5678 {
5679 if (CornerIndex != Index)
5680 {
5681 OverlappingCorners.Add(Index, CornerIndex);
5682 }
5683 }
5684 }
5685 OverlappingCorners.FinishAdding();
5686
5687 // Generate new UVs
5688 FLayoutUVRawMeshView TempMeshView(TempMesh, 0, 1);
5689 FLayoutUV Packer(TempMeshView);
5690 Packer.FindCharts(OverlappingCorners);
5691
5692 bool bPackSuccess = Packer.FindBestPacking(FMath::Clamp(TextureResolution / 4, 32, 512));
5693 if (bPackSuccess)
5694 {
5695 Packer.CommitPackedUVs();
5696 // Save generated UVs
5697 OutTexCoords = TempMesh.WedgeTexCoords[1];
5698 }
5699 return bPackSuccess;
5700}
5701
5702void FMeshUtilities::CalculateTangents(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, const TArray<FVector2D>& InUVs, const TArray<uint32>& InSmoothingGroupIndices, const uint32 InTangentOptions, TArray<FVector>& OutTangentX, TArray<FVector>& OutTangentY, TArray<FVector>& OutNormals) const
5703{
5704 const float ComparisonThreshold = (InTangentOptions & ETangentOptions::IgnoreDegenerateTriangles ) ? THRESH_POINTS_ARE_SAME : 0.0f;
5705
5706 FOverlappingCorners OverlappingCorners;
5707 FindOverlappingCorners(OverlappingCorners, InVertices, InIndices, ComparisonThreshold);
5708
5709 if ( InTangentOptions & ETangentOptions::UseMikkTSpace )
5710 {
5711 ComputeTangents_MikkTSpace(InVertices, InIndices, InUVs, InSmoothingGroupIndices, OverlappingCorners, OutTangentX, OutTangentY, OutNormals, InTangentOptions);
5712 }
5713 else
5714 {
5715 ComputeTangents(InVertices, InIndices, InUVs, InSmoothingGroupIndices, OverlappingCorners, OutTangentX, OutTangentY, OutNormals, InTangentOptions);
5716 }
5717}
5718
5719void FMeshUtilities::CalculateNormals(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, const TArray<FVector2D>& InUVs, const TArray<uint32>& InSmoothingGroupIndices, const uint32 InTangentOptions, TArray<FVector>& OutNormals) const
5720{
5721 const float ComparisonThreshold = (InTangentOptions & ETangentOptions::IgnoreDegenerateTriangles ) ? THRESH_POINTS_ARE_SAME : 0.0f;
5722
5723 FOverlappingCorners OverlappingCorners;
5724 FindOverlappingCorners(OverlappingCorners, InVertices, InIndices, ComparisonThreshold);
5725
5726 ComputeNormals(InVertices, InIndices, InUVs, InSmoothingGroupIndices, OverlappingCorners, OutNormals, InTangentOptions);
5727}
5728
5729void FMeshUtilities::CalculateOverlappingCorners(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, bool bIgnoreDegenerateTriangles, FOverlappingCorners& OutOverlappingCorners) const
5730{
5731 const float ComparisonThreshold = bIgnoreDegenerateTriangles ? THRESH_POINTS_ARE_SAME : 0.f;
5732 FindOverlappingCorners(OutOverlappingCorners, InVertices, InIndices, ComparisonThreshold);
5733}
5734
5735
5736void FMeshUtilities::GenerateRuntimeSkinWeightData(const FSkeletalMeshLODModel* ImportedModel, const TArray<FRawSkinWeight>& InRawSkinWeights, FRuntimeSkinWeightProfileData& InOutSkinWeightOverrideData) const
5737{
5738 const FSkeletalMeshLODModel& TargetLODModel = *ImportedModel;
5739
5740 // Make sure the number of verts of the LOD matches the provided number of skin weights
5741 if (InRawSkinWeights.Num() == TargetLODModel.NumVertices)
5742 {
5743 // Retrieve all vertices for this LOD
5744 TArray<FSoftSkinVertex> TargetVertices;
5745 TargetLODModel.GetVertices(TargetVertices);
5746
5747 // Determine how many influences each skinweight can contain
5748 const int32 NumInfluences = TargetLODModel.GetMaxBoneInfluences();
5749
5750 TArray<FRawSkinWeight> UniqueWeights;
5751 for (int32 VertexIndex = 0; VertexIndex < TargetVertices.Num(); ++VertexIndex)
5752 {
5753 // Take each original skin weight from the LOD and compare it with supplied alternative weight data
5754 const FRawSkinWeight& SourceSkinWeight = InRawSkinWeights[VertexIndex];
5755 const FSoftSkinVertex& TargetVertex = TargetVertices[VertexIndex];
5756
5757 bool bIsDifferent = false;
5758 for (int32 InfluenceIndex = 0; InfluenceIndex < NumInfluences; ++InfluenceIndex)
5759 {
5760 if (SourceSkinWeight.InfluenceBones[InfluenceIndex] != TargetVertex.InfluenceBones[InfluenceIndex]
5761 || SourceSkinWeight.InfluenceWeights[InfluenceIndex] != TargetVertex.InfluenceWeights[InfluenceIndex])
5762 {
5763 bIsDifferent = true;
5764 break;
5765 }
5766 }
5767
5768 if (bIsDifferent)
5769 {
5770 // Check whether or not there is already an override store which matches the new skin weight data
5771 int32 OverrideIndex = UniqueWeights.IndexOfByPredicate([SourceSkinWeight, NumInfluences](const FRawSkinWeight Override)
5772 {
5773 bool bSame = true;
5774 for (int32 InfluenceIndex = 0; InfluenceIndex < NumInfluences; ++InfluenceIndex)
5775 {
5776 bSame &= (Override.InfluenceBones[InfluenceIndex] == SourceSkinWeight.InfluenceBones[InfluenceIndex]);
5777 bSame &= (Override.InfluenceWeights[InfluenceIndex] == SourceSkinWeight.InfluenceWeights[InfluenceIndex]);
5778 }
5779
5780 return bSame;
5781 });
5782
5783 // If one hasn't been added yet, create a new one
5784 if (OverrideIndex == INDEX_NONE)
5785 {
5786 FRuntimeSkinWeightProfileData::FSkinWeightOverrideInfo& DeltaOverride = InOutSkinWeightOverrideData.OverridesInfo.AddDefaulted_GetRef();
5787
5788 // Store offset into array and total number of influences to read
5789 DeltaOverride.InfluencesOffset = InOutSkinWeightOverrideData.Weights.Num();
5790 DeltaOverride.NumInfluences = 0;
5791
5792 // Write out non-zero weighted influences only
5793 for (int32 InfluenceIndex = 0; InfluenceIndex < NumInfluences; ++InfluenceIndex)
5794 {
5795 if (SourceSkinWeight.InfluenceWeights[InfluenceIndex] > 0)
5796 {
5797 const uint32 Index = SourceSkinWeight.InfluenceBones[InfluenceIndex] << 16;
5798 const uint32 Weight = SourceSkinWeight.InfluenceWeights[InfluenceIndex];
5799 const uint32 Value = Index | Weight;
5800
5801 InOutSkinWeightOverrideData.Weights.Add(Value);
5802 ++DeltaOverride.NumInfluences;
5803 }
5804 }
5805
5806 OverrideIndex = InOutSkinWeightOverrideData.OverridesInfo.Num() - 1;
5807 UniqueWeights.Add(SourceSkinWeight);
5808 }
5809
5810 InOutSkinWeightOverrideData.VertexIndexOverrideIndex.Add(VertexIndex, OverrideIndex);
5811 }
5812 }
5813 }
5814}
5815
5816void FMeshUtilities::AddAnimationBlueprintEditorToolbarExtender()
5817{
5818 IAnimationBlueprintEditorModule& AnimationBlueprintEditorModule = FModuleManager::Get().LoadModuleChecked<IAnimationBlueprintEditorModule>("AnimationBlueprintEditor");
5819 auto& ToolbarExtenders = AnimationBlueprintEditorModule.GetAllAnimationBlueprintEditorToolbarExtenders();
5820
5821 ToolbarExtenders.Add(IAnimationBlueprintEditorModule::FAnimationBlueprintEditorToolbarExtender::CreateRaw(this, &FMeshUtilities::GetAnimationBlueprintEditorToolbarExtender));
5822 AnimationBlueprintEditorExtenderHandle = ToolbarExtenders.Last().GetHandle();
5823}
5824
5825void FMeshUtilities::RemoveAnimationBlueprintEditorToolbarExtender()
5826{
5827 IAnimationBlueprintEditorModule* AnimationBlueprintEditorModule = FModuleManager::Get().GetModulePtr<IAnimationBlueprintEditorModule>("AnimationBlueprintEditor");
5828 if (AnimationBlueprintEditorModule)
5829 {
5830 typedef IAnimationBlueprintEditorModule::FAnimationBlueprintEditorToolbarExtender DelegateType;
5831 AnimationBlueprintEditorModule->GetAllAnimationBlueprintEditorToolbarExtenders().RemoveAll([=](const DelegateType& In) { return In.GetHandle() == AnimationBlueprintEditorExtenderHandle; });
5832 }
5833}
5834
5835TSharedRef<FExtender> FMeshUtilities::GetAnimationBlueprintEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<IAnimationBlueprintEditor> InAnimationBlueprintEditor)
5836{
5837 TSharedRef<FExtender> Extender = MakeShareable(new FExtender);
5838
5839 if(InAnimationBlueprintEditor->GetBlueprintObj() && InAnimationBlueprintEditor->GetBlueprintObj()->BlueprintType != BPTYPE_Interface)
5840 {
5841 UMeshComponent* MeshComponent = InAnimationBlueprintEditor->GetPersonaToolkit()->GetPreviewMeshComponent();
5842
5843 Extender->AddToolBarExtension(
5844 "Asset",
5845 EExtensionHook::After,
5846 CommandList,
5847 FToolBarExtensionDelegate::CreateRaw(this, &FMeshUtilities::HandleAddSkeletalMeshActionExtenderToToolbar, MeshComponent)
5848 );
5849 }
5850
5851 return Extender;
5852}
5853
5854void FMeshUtilities::AddAnimationEditorToolbarExtender()
5855{
5856 IAnimationEditorModule& AnimationEditorModule = FModuleManager::Get().LoadModuleChecked<IAnimationEditorModule>("AnimationEditor");
5857 auto& ToolbarExtenders = AnimationEditorModule.GetAllAnimationEditorToolbarExtenders();
5858
5859 ToolbarExtenders.Add(IAnimationEditorModule::FAnimationEditorToolbarExtender::CreateRaw(this, &FMeshUtilities::GetAnimationEditorToolbarExtender));
5860 AnimationEditorExtenderHandle = ToolbarExtenders.Last().GetHandle();
5861}
5862
5863void FMeshUtilities::RemoveAnimationEditorToolbarExtender()
5864{
5865 IAnimationEditorModule* AnimationEditorModule = FModuleManager::Get().GetModulePtr<IAnimationEditorModule>("AnimationEditor");
5866 if (AnimationEditorModule)
5867 {
5868 typedef IAnimationEditorModule::FAnimationEditorToolbarExtender DelegateType;
5869 AnimationEditorModule->GetAllAnimationEditorToolbarExtenders().RemoveAll([=](const DelegateType& In) { return In.GetHandle() == AnimationEditorExtenderHandle; });
5870 }
5871}
5872
5873TSharedRef<FExtender> FMeshUtilities::GetAnimationEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<IAnimationEditor> InAnimationEditor)
5874{
5875 TSharedRef<FExtender> Extender = MakeShareable(new FExtender);
5876
5877 UMeshComponent* MeshComponent = InAnimationEditor->GetPersonaToolkit()->GetPreviewMeshComponent();
5878
5879 Extender->AddToolBarExtension(
5880 "Asset",
5881 EExtensionHook::After,
5882 CommandList,
5883 FToolBarExtensionDelegate::CreateRaw(this, &FMeshUtilities::HandleAddSkeletalMeshActionExtenderToToolbar, MeshComponent)
5884 );
5885
5886 return Extender;
5887}
5888
5889TSharedRef<FExtender> FMeshUtilities::GetSkeletalMeshEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<ISkeletalMeshEditor> InSkeletalMeshEditor)
5890{
5891 TSharedRef<FExtender> Extender = MakeShareable(new FExtender);
5892
5893 UMeshComponent* MeshComponent = InSkeletalMeshEditor->GetPersonaToolkit()->GetPreviewMeshComponent();
5894
5895 Extender->AddToolBarExtension(
5896 "Asset",
5897 EExtensionHook::After,
5898 CommandList,
5899 FToolBarExtensionDelegate::CreateRaw(this, &FMeshUtilities::HandleAddSkeletalMeshActionExtenderToToolbar, MeshComponent)
5900 );
5901
5902 return Extender;
5903}
5904
5905void FMeshUtilities::AddSkeletonEditorToolbarExtender()
5906{
5907 ISkeletonEditorModule& SkeletonEditorModule = FModuleManager::Get().LoadModuleChecked<ISkeletonEditorModule>("SkeletonEditor");
5908 auto& ToolbarExtenders = SkeletonEditorModule.GetAllSkeletonEditorToolbarExtenders();
5909
5910 ToolbarExtenders.Add(ISkeletonEditorModule::FSkeletonEditorToolbarExtender::CreateRaw(this, &FMeshUtilities::GetSkeletonEditorToolbarExtender));
5911 SkeletonEditorExtenderHandle = ToolbarExtenders.Last().GetHandle();
5912}
5913
5914void FMeshUtilities::RemoveSkeletonEditorToolbarExtender()
5915{
5916 ISkeletonEditorModule* SkeletonEditorModule = FModuleManager::Get().GetModulePtr<ISkeletonEditorModule>("SkeletonEditor");
5917 if (SkeletonEditorModule)
5918 {
5919 typedef ISkeletonEditorModule::FSkeletonEditorToolbarExtender DelegateType;
5920 SkeletonEditorModule->GetAllSkeletonEditorToolbarExtenders().RemoveAll([=](const DelegateType& In) { return In.GetHandle() == SkeletonEditorExtenderHandle; });
5921 }
5922}
5923
5924TSharedRef<FExtender> FMeshUtilities::GetSkeletonEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<ISkeletonEditor> InSkeletonEditor)
5925{
5926 TSharedRef<FExtender> Extender = MakeShareable(new FExtender);
5927
5928 UMeshComponent* MeshComponent = InSkeletonEditor->GetPersonaToolkit()->GetPreviewMeshComponent();
5929
5930 Extender->AddToolBarExtension(
5931 "Asset",
5932 EExtensionHook::After,
5933 CommandList,
5934 FToolBarExtensionDelegate::CreateRaw(this, &FMeshUtilities::HandleAddSkeletalMeshActionExtenderToToolbar, MeshComponent)
5935 );
5936
5937 return Extender;
5938}
5939
5940
5941void FMeshUtilities::HandleAddSkeletalMeshActionExtenderToToolbar(FToolBarBuilder& ParentToolbarBuilder, UMeshComponent* InMeshComponent)
5942{
5943 ParentToolbarBuilder.AddToolBarButton(
5944 FUIAction(FExecuteAction::CreateLambda([this, InMeshComponent]()
5945 {
5946 ConvertMeshesToStaticMesh(TArray<UMeshComponent*>({ InMeshComponent }), InMeshComponent->GetComponentToWorld());
5947 })),
5948 NAME_None,
5949 LOCTEXT("MakeStaticMesh", "Make Static Mesh"),
5950 LOCTEXT("MakeStaticMeshTooltip", "Make a new static mesh out of the preview's current pose."),
5951 FSlateIcon("EditorStyle", "Persona.ConvertToStaticMesh")
5952 );
5953}
5954
5955void FMeshUtilities::AddLevelViewportMenuExtender()
5956{
5957 FLevelEditorModule& LevelEditorModule = FModuleManager::Get().LoadModuleChecked<FLevelEditorModule>("LevelEditor");
5958 auto& MenuExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders();
5959
5960 MenuExtenders.Add(FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateRaw(this, &FMeshUtilities::GetLevelViewportContextMenuExtender));
5961 LevelViewportExtenderHandle = MenuExtenders.Last().GetHandle();
5962}
5963
5964void FMeshUtilities::RemoveLevelViewportMenuExtender()
5965{
5966 if (LevelViewportExtenderHandle.IsValid())
5967 {
5968 FLevelEditorModule* LevelEditorModule = FModuleManager::Get().GetModulePtr<FLevelEditorModule>("LevelEditor");
5969 if (LevelEditorModule)
5970 {
5971 typedef FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors DelegateType;
5972 LevelEditorModule->GetAllLevelViewportContextMenuExtenders().RemoveAll([=](const DelegateType& In) { return In.GetHandle() == LevelViewportExtenderHandle; });
5973 }
5974 }
5975}
5976
5977/** Util for getting all MeshComponents from a supplied set of Actors */
5978void GetSkinnedAndStaticMeshComponentsFromActors(const TArray<AActor*> InActors, TArray<UMeshComponent*>& OutMeshComponents)
5979{
5980 for (AActor* Actor : InActors)
5981 {
5982 // add all components from this actor
5983 TInlineComponentArray<UMeshComponent*> ActorComponents(Actor);
5984 for (UMeshComponent* ActorComponent : ActorComponents)
5985 {
5986 if (ActorComponent->IsA(USkinnedMeshComponent::StaticClass()) || ActorComponent->IsA(UStaticMeshComponent::StaticClass()))
5987 {
5988 OutMeshComponents.AddUnique(ActorComponent);
5989 }
5990 }
5991
5992 // add all attached actors
5993 TArray<AActor*> AttachedActors;
5994 Actor->GetAttachedActors(AttachedActors);
5995 for (AActor* AttachedActor : AttachedActors)
5996 {
5997 TInlineComponentArray<UMeshComponent*> AttachedActorComponents(AttachedActor);
5998 for (UMeshComponent* AttachedActorComponent : AttachedActorComponents)
5999 {
6000 if (AttachedActorComponent->IsA(USkinnedMeshComponent::StaticClass()) || AttachedActorComponent->IsA(UStaticMeshComponent::StaticClass()))
6001 {
6002 OutMeshComponents.AddUnique(AttachedActorComponent);
6003 }
6004 }
6005 }
6006 }
6007}
6008
6009TSharedRef<FExtender> FMeshUtilities::GetLevelViewportContextMenuExtender(const TSharedRef<FUICommandList> CommandList, const TArray<AActor*> InActors)
6010{
6011 TSharedRef<FExtender> Extender = MakeShareable(new FExtender);
6012
6013 if (InActors.Num() > 0)
6014 {
6015 TArray<UMeshComponent*> Components;
6016 GetSkinnedAndStaticMeshComponentsFromActors(InActors, Components);
6017 if (Components.Num() > 0)
6018 {
6019 FText ActorName = InActors.Num() == 1 ? FText::Format(LOCTEXT("ActorNameSingular", "\"{0}\""), FText::FromString(InActors[0]->GetActorLabel())) : LOCTEXT("ActorNamePlural", "Actors");
6020
6021 FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
6022 TSharedRef<FUICommandList> LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions();
6023
6024 Extender->AddMenuExtension("ActorControl", EExtensionHook::After, LevelEditorCommandBindings, FMenuExtensionDelegate::CreateLambda(
6025 [this, ActorName, InActors](FMenuBuilder& MenuBuilder) {
6026
6027 MenuBuilder.AddMenuEntry(
6028 FText::Format(LOCTEXT("ConvertSelectedActorsToStaticMeshText", "Convert {0} To Static Mesh"), ActorName),
6029 LOCTEXT("ConvertSelectedActorsToStaticMeshTooltip", "Convert the selected actor's meshes to a new Static Mesh asset. Supports static and skeletal meshes."),
6030 FSlateIcon(),
6031 FUIAction(FExecuteAction::CreateRaw(this, &FMeshUtilities::ConvertActorMeshesToStaticMeshUIAction, InActors))
6032 );
6033 })
6034 );
6035 }
6036 }
6037
6038 return Extender;
6039}
6040
6041void FMeshUtilities::ConvertActorMeshesToStaticMeshUIAction(const TArray<AActor*> InActors)
6042{
6043 TArray<UMeshComponent*> MeshComponents;
6044
6045 GetSkinnedAndStaticMeshComponentsFromActors(InActors, MeshComponents);
6046
6047 auto GetActorRootTransform = [](AActor* InActor)
6048 {
6049 FTransform RootTransform(FTransform::Identity);
6050 if (ACharacter* Character = Cast<ACharacter>(InActor))
6051 {
6052 RootTransform = Character->GetTransform();
6053 RootTransform.SetLocation(RootTransform.GetLocation() - FVector(0.0f, 0.0f, Character->GetCapsuleComponent()->GetScaledCapsuleHalfHeight()));
6054 }
6055 else
6056 {
6057 // otherwise just use the actor's origin
6058 RootTransform = InActor->GetTransform();
6059 }
6060
6061 return RootTransform;
6062 };
6063
6064 // now pick a root transform
6065 FTransform RootTransform(FTransform::Identity);
6066 if (InActors.Num() == 1)
6067 {
6068 RootTransform = GetActorRootTransform(InActors[0]);
6069 }
6070 else
6071 {
6072 // multiple actors use the average of their origins, with Z being the min of all origins. Rotation is identity for simplicity
6073 FVector Location(FVector::ZeroVector);
6074 float MinZ = FLT_MAX;
6075 for (AActor* Actor : InActors)
6076 {
6077 FTransform ActorTransform(GetActorRootTransform(Actor));
6078 Location += ActorTransform.GetLocation();
6079 MinZ = FMath::Min(ActorTransform.GetLocation().Z, MinZ);
6080 }
6081 Location /= (float)InActors.Num();
6082 Location.Z = MinZ;
6083
6084 RootTransform.SetLocation(Location);
6085 }
6086
6087 UStaticMesh* StaticMesh = ConvertMeshesToStaticMesh(MeshComponents, RootTransform);
6088
6089 // Also notify the content browser that the new assets exists
6090 if (StaticMesh != nullptr)
6091 {
6092 FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
6093 ContentBrowserModule.Get().SyncBrowserToAssets(TArray<UObject*>({ StaticMesh }), true);
6094 }
6095}
6096
6097/************************************************************************/
6098/* DEPRECATED FUNCTIONALITY */
6099/************************************************************************/
6100IMeshReduction* FMeshUtilities::GetStaticMeshReductionInterface()
6101{
6102 IMeshReductionManagerModule& Module = FModuleManager::Get().LoadModuleChecked<IMeshReductionManagerModule>("MeshReductionInterface");
6103 return Module.GetStaticMeshReductionInterface();
6104}
6105
6106IMeshReduction* FMeshUtilities::GetSkeletalMeshReductionInterface()
6107{
6108 IMeshReductionManagerModule& Module = FModuleManager::Get().LoadModuleChecked<IMeshReductionManagerModule>("MeshReductionInterface");
6109 return Module.GetSkeletalMeshReductionInterface();
6110}
6111
6112IMeshMerging* FMeshUtilities::GetMeshMergingInterface()
6113{
6114 IMeshReductionManagerModule& Module = FModuleManager::Get().LoadModuleChecked<IMeshReductionManagerModule>("MeshReductionInterface");
6115 return Module.GetMeshMergingInterface();
6116}
6117
6118void FMeshUtilities::MergeActors(
6119 const TArray<AActor*>& SourceActors,
6120 const FMeshMergingSettings& InSettings,
6121 UPackage* InOuter,
6122 const FString& InBasePackageName,
6123 TArray<UObject*>& OutAssetsToSync,
6124 FVector& OutMergedActorLocation,
6125 bool bSilent) const
6126{
6127 checkf(SourceActors.Num(), TEXT("No actors supplied for merging"));
6128
6129 // Collect all primitive components
6130 TInlineComponentArray<UPrimitiveComponent*> PrimComps;
6131 for (AActor* Actor : SourceActors)
6132 {
6133 Actor->GetComponents<UPrimitiveComponent>(PrimComps);
6134 }
6135
6136 // Filter only components we want (static mesh and shape)
6137 TArray<UPrimitiveComponent*> ComponentsToMerge;
6138 for (UPrimitiveComponent* PrimComponent : PrimComps)
6139 {
6140 UStaticMeshComponent* MeshComponent = Cast<UStaticMeshComponent>(PrimComponent);
6141 if (MeshComponent &&
6142 MeshComponent->GetStaticMesh() != nullptr &&
6143 MeshComponent->GetStaticMesh()->GetNumSourceModels() > 0)
6144 {
6145 ComponentsToMerge.Add(MeshComponent);
6146 }
6147
6148 UShapeComponent* ShapeComponent = Cast<UShapeComponent>(PrimComponent);
6149 if (ShapeComponent)
6150 {
6151 ComponentsToMerge.Add(ShapeComponent);
6152 }
6153 }
6154
6155 checkf(SourceActors.Num(), TEXT("No valid components found in actors supplied for merging"));
6156
6157 UWorld* World = SourceActors[0]->GetWorld();
6158 checkf(World != nullptr, TEXT("Invalid world retrieved from Actor"));
6159 const float ScreenSize = TNumericLimits<float>::Max();
6160
6161 const IMeshMergeUtilities& Module = FModuleManager::Get().LoadModuleChecked<IMeshMergeModule>("MeshMergeUtilities").GetUtilities();
6162 Module.MergeComponentsToStaticMesh(ComponentsToMerge, World, InSettings, nullptr, InOuter, InBasePackageName, OutAssetsToSync, OutMergedActorLocation, ScreenSize, bSilent);
6163}
6164
6165void FMeshUtilities::MergeStaticMeshComponents(
6166 const TArray<UStaticMeshComponent*>& ComponentsToMerge,
6167 UWorld* World,
6168 const FMeshMergingSettings& InSettings,
6169 UPackage* InOuter,
6170 const FString& InBasePackageName,
6171 TArray<UObject*>& OutAssetsToSync,
6172 FVector& OutMergedActorLocation,
6173 const float ScreenSize,
6174 bool bSilent /*= false*/) const
6175{
6176 const IMeshMergeUtilities& Module = FModuleManager::Get().LoadModuleChecked<IMeshMergeModule>("MeshMergeUtilities").GetUtilities();
6177
6178 // Convert array of StaticMeshComponents to PrimitiveComponents
6179 TArray<UPrimitiveComponent*> PrimCompsToMerge;
6180 Algo::Transform(ComponentsToMerge, PrimCompsToMerge, [](UStaticMeshComponent* StaticMeshComp) { return StaticMeshComp; });
6181
6182 Module.MergeComponentsToStaticMesh(PrimCompsToMerge, World, InSettings, nullptr, InOuter, InBasePackageName, OutAssetsToSync, OutMergedActorLocation, ScreenSize, bSilent);
6183}
6184
6185void FMeshUtilities::CreateProxyMesh(const TArray<AActor*>& InActors, const struct FMeshProxySettings& InMeshProxySettings, UPackage* InOuter, const FString& InProxyBasePackageName, const FGuid InGuid, FCreateProxyDelegate InProxyCreatedDelegate, const bool bAllowAsync,
6186 const float ScreenAreaSize /*= 1.0f*/)
6187{
6188 const IMeshMergeUtilities& Module = FModuleManager::Get().LoadModuleChecked<IMeshMergeModule>("MeshMergeUtilities").GetUtilities();
6189 Module.CreateProxyMesh(InActors, InMeshProxySettings, InOuter, InProxyBasePackageName, InGuid, InProxyCreatedDelegate, bAllowAsync, ScreenAreaSize);
6190}
6191
6192bool FMeshUtilities::GenerateUniqueUVsForStaticMesh(const FRawMesh& RawMesh, int32 TextureResolution, bool bMergeIdenticalMaterials, TArray<FVector2D>& OutTexCoords) const
6193{
6194 // Create a copy of original mesh (only copy necessary data)
6195 FRawMesh TempMesh;
6196 TempMesh.VertexPositions = RawMesh.VertexPositions;
6197
6198 // Remove all duplicate faces if we are merging identical materials
6199 const int32 NumFaces = RawMesh.FaceMaterialIndices.Num();
6200 TArray<int32> DuplicateFaceRecords;
6201
6202 if(bMergeIdenticalMaterials)
6203 {
6204 TArray<int32> UniqueFaceIndices;
6205 UniqueFaceIndices.Reserve(NumFaces);
6206 DuplicateFaceRecords.SetNum(NumFaces);
6207
6208 TempMesh.WedgeTexCoords[0].Reserve(RawMesh.WedgeTexCoords[0].Num());
6209 TempMesh.WedgeIndices.Reserve(RawMesh.WedgeIndices.Num());
6210
6211 // insert only non-duplicate faces
6212 for(int32 FaceIndex = 0; FaceIndex < NumFaces; ++FaceIndex)
6213 {
6214 bool bFound = false;
6215 int32 UniqueFaceIndex = 0;
6216 for( ; UniqueFaceIndex < UniqueFaceIndices.Num(); ++UniqueFaceIndex)
6217 {
6218 int32 TestIndex = UniqueFaceIndices[UniqueFaceIndex];
6219
6220 if (TestIndex != FaceIndex &&
6221 RawMesh.FaceMaterialIndices[FaceIndex] == RawMesh.FaceMaterialIndices[TestIndex] &&
6222 RawMesh.WedgeTexCoords[0][(FaceIndex * 3) + 0] == RawMesh.WedgeTexCoords[0][(TestIndex * 3) + 0] &&
6223 RawMesh.WedgeTexCoords[0][(FaceIndex * 3) + 1] == RawMesh.WedgeTexCoords[0][(TestIndex * 3) + 1] &&
6224 RawMesh.WedgeTexCoords[0][(FaceIndex * 3) + 2] == RawMesh.WedgeTexCoords[0][(TestIndex * 3) + 2])
6225 {
6226 bFound = true;
6227 break;
6228 }
6229 }
6230
6231 if(!bFound)
6232 {
6233 UniqueFaceIndices.Add(FaceIndex);
6234 TempMesh.WedgeTexCoords[0].Add(RawMesh.WedgeTexCoords[0][(FaceIndex * 3) + 0]);
6235 TempMesh.WedgeTexCoords[0].Add(RawMesh.WedgeTexCoords[0][(FaceIndex * 3) + 1]);
6236 TempMesh.WedgeTexCoords[0].Add(RawMesh.WedgeTexCoords[0][(FaceIndex * 3) + 2]);
6237 TempMesh.WedgeIndices.Add(RawMesh.WedgeIndices[(FaceIndex * 3) + 0]);
6238 TempMesh.WedgeIndices.Add(RawMesh.WedgeIndices[(FaceIndex * 3) + 1]);
6239 TempMesh.WedgeIndices.Add(RawMesh.WedgeIndices[(FaceIndex * 3) + 2]);
6240
6241 DuplicateFaceRecords[FaceIndex] = UniqueFaceIndices.Num() - 1;
6242 }
6243 else
6244 {
6245 DuplicateFaceRecords[FaceIndex] = UniqueFaceIndex;
6246 }
6247 }
6248 }
6249 else
6250 {
6251 TempMesh.WedgeTexCoords[0] = RawMesh.WedgeTexCoords[0];
6252 TempMesh.WedgeIndices = RawMesh.WedgeIndices;
6253 }
6254
6255 // Find overlapping corners for UV generator. Allow some threshold - this should not produce any error in a case if resulting
6256 // mesh will not merge these vertices.
6257 FOverlappingCorners OverlappingCorners;
6258 FModuleManager::Get().LoadModuleChecked<IMeshUtilities>("MeshUtilities").FindOverlappingCorners(OverlappingCorners, TempMesh.VertexPositions, TempMesh.WedgeIndices, THRESH_POINTS_ARE_SAME);
6259
6260 // Generate new UVs
6261 FLayoutUVRawMeshView TempMeshView(TempMesh, 0, 1);
6262 FLayoutUV Packer(TempMeshView);
6263 Packer.FindCharts(OverlappingCorners);
6264
6265 bool bPackSuccess = Packer.FindBestPacking(FMath::Clamp(TextureResolution / 4, 32, 512));
6266 if (bPackSuccess)
6267 {
6268 Packer.CommitPackedUVs();
6269
6270 if(bMergeIdenticalMaterials)
6271 {
6272 // re-duplicate faces
6273 OutTexCoords.SetNum(RawMesh.WedgeTexCoords[0].Num());
6274
6275 for(int32 FaceIndex = 0; FaceIndex < DuplicateFaceRecords.Num(); ++FaceIndex)
6276 {
6277 int32 SourceFaceIndex = DuplicateFaceRecords[FaceIndex];
6278
6279 OutTexCoords[(FaceIndex * 3) + 0] = TempMesh.WedgeTexCoords[1][(SourceFaceIndex * 3) + 0];
6280 OutTexCoords[(FaceIndex * 3) + 1] = TempMesh.WedgeTexCoords[1][(SourceFaceIndex * 3) + 1];
6281 OutTexCoords[(FaceIndex * 3) + 2] = TempMesh.WedgeTexCoords[1][(SourceFaceIndex * 3) + 2];
6282 }
6283 }
6284 else
6285 {
6286 // Save generated UVs
6287 OutTexCoords = TempMesh.WedgeTexCoords[1];
6288 }
6289 }
6290
6291 return bPackSuccess;
6292}
6293
6294bool FMeshUtilities::GenerateUniqueUVsForStaticMesh(const FRawMesh& RawMesh, int32 TextureResolution, TArray<FVector2D>& OutTexCoords) const
6295{
6296 return GenerateUniqueUVsForStaticMesh(RawMesh, TextureResolution, false, OutTexCoords);
6297}
6298
6299void FMeshUtilities::FlattenMaterialsWithMeshData(TArray<UMaterialInterface*>& InMaterials, TArray<FRawMeshExt>& InSourceMeshes, TMap<FMeshIdAndLOD, TArray<int32>>& InMaterialIndexMap, TArray<bool>& InMeshShouldBakeVertexData, const FMaterialProxySettings &InMaterialProxySettings, TArray<FFlattenMaterial> &OutFlattenedMaterials) const
6300{
6301 checkf(false, TEXT("Function is removed, use functionality in new MeshMergeUtilities Module"));
6302}
6303
6304#undef LOCTEXT_NAMESPACE
6305