This commit is contained in:
FishOrBear
2021-06-30 17:11:29 +08:00
commit 55fe2a8d29
67 changed files with 20096 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "MeshUtilities2",
"Description": "",
"Category": "Other",
"CreatedBy": "ChenX",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": true,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": false,
"Modules": [
{
"Name": "MeshUtilities2",
"Type": "Runtime",
"LoadingPhase": "Default"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,88 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class MeshUtilities2 : ModuleRules
{
public MeshUtilities2(ReadOnlyTargetRules Target) : base(Target)
{
PublicDependencyModuleNames.AddRange(
new string[]
{
// "MaterialUtilities",
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"Engine",
"RawMesh",
"RenderCore", // For FPackedNormal
"SlateCore",
"Slate",
// "MaterialUtilities",
// "MeshBoneReduction",
// "EditorFramework",
// "UnrealEd",
"RHI",
//"HierarchicalLODUtilities",
"Landscape",
// "LevelEditor",
// "PropertyEditor",
// "EditorStyle",
// "GraphColor",
// "MeshBuilderCommon",
"MeshUtilitiesCommon",
"MeshDescription",
"StaticMeshDescription",
// "ToolMenus",
}
);
PublicIncludePathModuleNames.AddRange(
new string[]
{
"MeshMergeUtilities"
}
);
PrivateIncludePathModuleNames.AddRange(
new string[]
{
// "AnimationBlueprintEditor",
// "AnimationEditor",
// "MeshMergeUtilities",
// "MaterialBaking",
// "Persona",
// "SkeletalMeshEditor",
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// "AnimationBlueprintEditor",
// "AnimationEditor",
// "MeshMergeUtilities",
// "MaterialBaking",
// "SkeletalMeshEditor",
}
);
AddEngineThirdPartyPrivateStaticDependencies(Target, "nvTriStrip");
AddEngineThirdPartyPrivateStaticDependencies(Target, "ForsythTriOptimizer");
// AddEngineThirdPartyPrivateStaticDependencies(Target, "QuadricMeshReduction");
AddEngineThirdPartyPrivateStaticDependencies(Target, "MikkTSpace");
AddEngineThirdPartyPrivateStaticDependencies(Target, "nvTessLib");
if (Target.Platform == UnrealTargetPlatform.Win64)
{
AddEngineThirdPartyPrivateStaticDependencies(Target, "DX9");
}
AddEngineThirdPartyPrivateStaticDependencies(Target, "Embree3");
}
}

View File

@@ -0,0 +1,598 @@
#include "DistanceFieldAtlas2.h"
// #include "DistanceFieldAtlas.h"
// #include "HAL/RunnableThread.h"
// #include "HAL/Runnable.h"
// #include "Misc/App.h"
// #include "Serialization/MemoryReader.h"
// #include "Serialization/MemoryWriter.h"
#include "Modules/ModuleManager.h"
#include "StaticMeshResources.h"
#include "ProfilingDebugging/CookStats.h"
#include "Templates/UniquePtr.h"
#include "Engine/StaticMesh.h"
#include "Misc/AutomationTest.h"
// #include "Async/ParallelFor.h"
// #include "DistanceFieldDownsampling.h"
// #include "GlobalShader.h"
#include "RenderGraph.h"
#include "MeshCardRepresentation.h"
#include "Misc/QueuedThreadPoolWrapper.h"
// #include "Async/Async.h"
#include "ObjectCacheContext.h"
// #if WITH_EDITOR
// #include "DerivedDataCacheInterface.h"
#include "Engine/Public/DistanceFieldAtlas.h"
// #include "AssetCompilingManager.h"
#include "MeshCardRepresentation2.h"
#include "StaticMeshCompiler.h"
#include "MeshUtilities2/Public/MeshUtilities2.h"
// #endif
CSV_DEFINE_CATEGORY(DistanceField2, false);
#if ENABLE_COOK_STATS
namespace DistanceFieldCookStats
{
FCookStats::FDDCResourceUsageStats UsageStats;
static FCookStatsManager::FAutoRegisterCallback RegisterCookStats([](FCookStatsManager::AddStatFuncRef AddStat)
{
UsageStats.LogStats(AddStat, TEXT("DistanceField2.Usage"), TEXT(""));
});
}
#endif
FDistanceFieldAsyncQueue2* GDistanceFieldAsyncQueue2 = nullptr;
int32 GUseAsyncDistanceFieldBuildQueue2 = 1;
static FAutoConsoleVariableRef CVarAOAsyncBuildQueue(
TEXT("r.AOAsyncBuildQueue2"),
GUseAsyncDistanceFieldBuildQueue2,
TEXT("是否从网格异步构建距离场体数据。"),
ECVF_Default | ECVF_ReadOnly
);
//构建距离场
void BuildMeshDistanceField(UStaticMesh* StaticMesh)
{
//ref:StaticMesh.cpp 2782
auto RenderData = StaticMesh->GetRenderData();
if (RenderData->LODResources.IsValidIndex(0))
{
auto& LODResource = RenderData->LODResources[0];
if (!LODResource.DistanceFieldData)
{
LODResource.DistanceFieldData = new FDistanceFieldVolumeData();
LODResource.DistanceFieldData->AssetName = StaticMesh->GetFName();
}
// We don't actually build the resource until later, so only track the cycles used here.
// COOK_STAT(Timer.TrackCyclesOnly());
FAsyncDistanceFieldTask2* NewTask = new FAsyncDistanceFieldTask2;
NewTask->DDCKey = "";
// check(Mesh && GenerateSource);
// NewTask->TargetPlatform = RunningPlatform;
NewTask->StaticMesh = StaticMesh;
NewTask->GenerateSource = StaticMesh; //GenerateSource;
NewTask->DistanceFieldResolutionScale = 2; //DistanceFieldResolutionScale;
NewTask->bGenerateDistanceFieldAsIfTwoSided = false; // bGenerateDistanceFieldAsIfTwoSided;
NewTask->GeneratedVolumeData = new FDistanceFieldVolumeData(); //;给一个新的用于存放
NewTask->GeneratedVolumeData->AssetName = StaticMesh->GetFName();
NewTask->GeneratedVolumeData->bAsyncBuilding = true;
for (int32 MaterialIndex = 0; MaterialIndex < StaticMesh->GetStaticMaterials().Num(); MaterialIndex++)
{
FSignedDistanceFieldBuildMaterialData2 MaterialData;
// Default material blend mode
MaterialData.BlendMode = BLEND_Opaque;
MaterialData.bTwoSided = false;
if (StaticMesh->GetStaticMaterials()[MaterialIndex].MaterialInterface)
{
MaterialData.BlendMode = StaticMesh->GetStaticMaterials()[MaterialIndex].MaterialInterface->GetBlendMode();
MaterialData.bTwoSided = StaticMesh->GetStaticMaterials()[MaterialIndex].MaterialInterface->IsTwoSided();
}
NewTask->MaterialBlendModes.Add(MaterialData);
}
// // Nanite overrides source static mesh with a coarse representation. Need to load original data before we build the mesh SDF.
// if (StaticMesh->NaniteSettings.bEnabled)
// {
// IMeshBuilderModule& MeshBuilderModule = IMeshBuilderModule::GetForPlatform(TargetPlatform);
// if (!MeshBuilderModule.BuildMeshVertexPositions(Mesh, NewTask->SourceMeshData.TriangleIndices, NewTask->SourceMeshData.VertexPositions))
// {
// UE_LOG(LogStaticMesh, Error, TEXT("Failed to build static mesh. See previous line(s) for details."));
// }
// }
GDistanceFieldAsyncQueue2->AddTask(NewTask);
}
}
void BuildMeshCardRepresentation(UStaticMesh* StaticMeshAsset, FStaticMeshRenderData& RenderData, FSourceMeshDataForDerivedDataTask* OptionalSourceMeshData)
{
if (RenderData.LODResources.IsValidIndex(0))
{
if (!RenderData.LODResources[0].CardRepresentationData)
{
RenderData.LODResources[0].CardRepresentationData = new FCardRepresentationData();
}
// const FMeshBuildSettings& BuildSettings = StaticMeshAsset->GetSourceModel(0).BuildSettings;
// UStaticMesh* MeshToGenerateFrom = StaticMeshAsset;
// We don't actually build the resource until later, so only track the cycles used here.
// COOK_STAT(Timer.TrackCyclesOnly());
FAsyncCardRepresentationTask2* NewTask = new FAsyncCardRepresentationTask2;
// NewTask->DDCKey = InDDCKey;
check(StaticMeshAsset);
NewTask->StaticMesh = StaticMeshAsset;
NewTask->GenerateSource = StaticMeshAsset;
NewTask->GeneratedCardRepresentation = new FCardRepresentationData();
NewTask->bGenerateDistanceFieldAsIfTwoSided = false;
for (int32 MaterialIndex = 0; MaterialIndex < StaticMeshAsset->GetStaticMaterials().Num(); MaterialIndex++)
{
FSignedDistanceFieldBuildMaterialData2 MaterialData;
// Default material blend mode
MaterialData.BlendMode = BLEND_Opaque;
MaterialData.bTwoSided = false;
if (StaticMeshAsset->GetStaticMaterials()[MaterialIndex].MaterialInterface)
{
MaterialData.BlendMode = StaticMeshAsset->GetStaticMaterials()[MaterialIndex].MaterialInterface->GetBlendMode();
MaterialData.bTwoSided = StaticMeshAsset->GetStaticMaterials()[MaterialIndex].MaterialInterface->IsTwoSided();
}
NewTask->MaterialBlendModes.Add(MaterialData);
}
// Nanite overrides source static mesh with a coarse representation. Need to load original data before we build the mesh SDF.
if (OptionalSourceMeshData)
{
NewTask->SourceMeshData = *OptionalSourceMeshData;
}
// else if (StaticMeshAsset->NaniteSettings.bEnabled)
// {
// IMeshBuilderModule& MeshBuilderModule = IMeshBuilderModule::GetForPlatform(TargetPlatform);
// if (!MeshBuilderModule.BuildMeshVertexPositions(Mesh, NewTask->SourceMeshData.TriangleIndices, NewTask->SourceMeshData.VertexPositions))
// {
// UE_LOG(LogStaticMesh, Error, TEXT("Failed to build static mesh. See previous line(s) for details."));
// }
// }
GCardRepresentationAsyncQueue2->AddTask(NewTask);
}
}
FAsyncDistanceFieldTask2::FAsyncDistanceFieldTask2()
: StaticMesh(nullptr)
, GenerateSource(nullptr)
, DistanceFieldResolutionScale(0.0f)
, bGenerateDistanceFieldAsIfTwoSided(false)
, GeneratedVolumeData(nullptr)
{
}
FDistanceFieldAsyncQueue2::FDistanceFieldAsyncQueue2()
{
MeshUtilities = NULL;
// #if WITH_EDITOR
// const int32 MaxConcurrency = -1;
// // In Editor, we allow faster compilation by letting the asset compiler's scheduler organize work.
// ThreadPool = MakeUnique<FQueuedThreadPoolWrapper>(FAssetCompilingManager::Get().GetThreadPool(), MaxConcurrency, [](EQueuedWorkPriority) { return EQueuedWorkPriority::Lowest; });
// #else
const int32 MaxConcurrency = -1;
ThreadPool = MakeUnique<FQueuedThreadPoolWrapper>(GThreadPool, MaxConcurrency, [](EQueuedWorkPriority) { return EQueuedWorkPriority::Lowest; });
// #endif
}
FDistanceFieldAsyncQueue2::~FDistanceFieldAsyncQueue2()
{
}
void FAsyncDistanceFieldTaskWorker2::DoWork()
{
// Put on background thread to avoid interfering with game-thread bound tasks
FQueuedThreadPoolTaskGraphWrapper TaskGraphWrapper(ENamedThreads::AnyBackgroundThreadNormalTask);
GDistanceFieldAsyncQueue2->Build(&Task, TaskGraphWrapper);
}
void FDistanceFieldAsyncQueue2::CancelBackgroundTask(TArray<FAsyncDistanceFieldTask2*> Tasks)
{
// Do all the cancellation first to make sure none of these tasks
// get scheduled as we're waiting for completion.
for (FAsyncDistanceFieldTask2* Task : Tasks)
{
if (Task->AsyncTask)
{
Task->AsyncTask->Cancel();
}
}
for (FAsyncDistanceFieldTask2* Task : Tasks)
{
if (Task->AsyncTask)
{
Task->AsyncTask->EnsureCompletion();
Task->AsyncTask.Reset();
}
}
}
void FDistanceFieldAsyncQueue2::StartBackgroundTask(FAsyncDistanceFieldTask2* Task)
{
check(Task->AsyncTask == nullptr);
Task->AsyncTask = MakeUnique<FAsyncTask<FAsyncDistanceFieldTaskWorker2>>(*Task);
Task->AsyncTask->StartBackgroundTask(ThreadPool.Get(), EQueuedWorkPriority::Lowest);
}
void FDistanceFieldAsyncQueue2::ProcessPendingTasks()
{
FScopeLock Lock(&CriticalSection);
TArray<FAsyncDistanceFieldTask2*> Tasks = MoveTemp(PendingTasks);
for (FAsyncDistanceFieldTask2* Task : Tasks)
{
if (Task->GenerateSource && Task->GenerateSource->IsCompiling())
{
PendingTasks.Add(Task);
}
else
{
StartBackgroundTask(Task);
}
}
}
void FDistanceFieldAsyncQueue2::AddTask(FAsyncDistanceFieldTask2* Task)
{
// #if WITH_EDITOR
if (!MeshUtilities)
{
MeshUtilities = &FModuleManager::Get().LoadModuleChecked<IMeshUtilities2>(TEXT("MeshUtilities2"));
}
{
// Array protection when called from multiple threads
FScopeLock Lock(&CriticalSection);
ReferencedTasks.Add(Task);
}
// The Source Mesh's RenderData is not yet ready, postpone the build
if (Task->GenerateSource->IsCompiling())
{
// Array protection when called from multiple threads
FScopeLock Lock(&CriticalSection);
PendingTasks.Add(Task);
}
else
{
// If we're already in worker threads, there is no need to launch an async task.
if (GUseAsyncDistanceFieldBuildQueue2 || !IsInGameThread())
{
StartBackgroundTask(Task);
}
else
{
// To avoid deadlocks, we must queue the inner build tasks on another thread pool, so use the task graph.
// Put on background thread to avoid interfering with game-thread bound tasks
FQueuedThreadPoolTaskGraphWrapper TaskGraphWrapper(ENamedThreads::AnyBackgroundThreadNormalTask);
Build(Task, TaskGraphWrapper);
}
}
// #else
// UE_LOG(LogStaticMesh,Fatal,TEXT("Tried to build a distance field without editor support (this should have been done during cooking)"));
// #endif
}
void FDistanceFieldAsyncQueue2::CancelBuild(UStaticMesh* StaticMesh)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FDistanceFieldAsyncQueue2::CancelBuild)
TArray<FAsyncDistanceFieldTask2*> TasksToCancel;
{
FScopeLock Lock(&CriticalSection);
TArray<FAsyncDistanceFieldTask2*> Tasks = MoveTemp(PendingTasks);
PendingTasks.Reserve(Tasks.Num());
for (FAsyncDistanceFieldTask2* Task : Tasks)
{
if (Task->GenerateSource != StaticMesh && Task->StaticMesh != StaticMesh)
{
PendingTasks.Add(Task);
}
}
Tasks = MoveTemp(ReferencedTasks);
ReferencedTasks.Reserve(Tasks.Num());
for (FAsyncDistanceFieldTask2* Task : Tasks)
{
if (Task->GenerateSource != StaticMesh && Task->StaticMesh != StaticMesh)
{
ReferencedTasks.Add(Task);
}
else
{
TasksToCancel.Add(Task);
}
}
}
CancelBackgroundTask(TasksToCancel);
for (FAsyncDistanceFieldTask2* Task : TasksToCancel)
{
if (Task->GeneratedVolumeData != nullptr)
{
// Rendering thread may still be referencing the old one, use the deferred cleanup interface to delete it next frame when it is safe
BeginCleanup(Task->GeneratedVolumeData);
}
delete Task;
}
}
void FDistanceFieldAsyncQueue2::CancelAllOutstandingBuilds()
{
TRACE_CPUPROFILER_EVENT_SCOPE(FDistanceFieldAsyncQueue2::CancelAllOutstandingBuilds)
TArray<FAsyncDistanceFieldTask2*> OutstandingTasks;
{
FScopeLock Lock(&CriticalSection);
PendingTasks.Empty();
OutstandingTasks = MoveTemp(ReferencedTasks);
}
CancelBackgroundTask(OutstandingTasks);
for (FAsyncDistanceFieldTask2* Task : OutstandingTasks)
{
delete Task;
}
}
void FDistanceFieldAsyncQueue2::RescheduleBackgroundTask(FAsyncDistanceFieldTask2* InTask, EQueuedWorkPriority InPriority)
{
if (InTask->AsyncTask)
{
if (InTask->AsyncTask->GetPriority() != InPriority)
{
InTask->AsyncTask->Reschedule(GThreadPool, InPriority);
}
}
}
void FDistanceFieldAsyncQueue2::BlockUntilBuildComplete(UStaticMesh* StaticMesh, bool bWarnIfBlocked)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FDistanceFieldAsyncQueue2::BlockUntilBuildComplete)
// We will track the wait time here, but only the cycles used.
// This function is called whether or not an async task is pending,
// so we have to look elsewhere to properly count how many resources have actually finished building.
COOK_STAT(auto Timer = DistanceFieldCookStats::UsageStats.TimeAsyncWait());
COOK_STAT(Timer.TrackCyclesOnly());
bool bReferenced = false;
bool bHadToBlock = false;
double StartTime = 0;
TSet<UStaticMesh*> RequiredFinishCompilation;
do
{
ProcessAsyncTasks();
bReferenced = false;
RequiredFinishCompilation.Reset();
// Reschedule the tasks we're waiting on as highest prio
{
FScopeLock Lock(&CriticalSection);
for (int TaskIndex = 0; TaskIndex < ReferencedTasks.Num(); TaskIndex++)
{
if (ReferencedTasks[TaskIndex]->StaticMesh == StaticMesh ||
ReferencedTasks[TaskIndex]->GenerateSource == StaticMesh)
{
bReferenced = true;
// If the task we are waiting on depends on other static meshes
// we need to force finish them too.
// #if WITH_EDITOR
if (ReferencedTasks[TaskIndex]->GenerateSource != nullptr &&
ReferencedTasks[TaskIndex]->GenerateSource->IsCompiling())
{
RequiredFinishCompilation.Add(ReferencedTasks[TaskIndex]->GenerateSource);
}
if (ReferencedTasks[TaskIndex]->StaticMesh != nullptr &&
ReferencedTasks[TaskIndex]->StaticMesh->IsCompiling())
{
RequiredFinishCompilation.Add(ReferencedTasks[TaskIndex]->StaticMesh);
}
// #endif
RescheduleBackgroundTask(ReferencedTasks[TaskIndex], EQueuedWorkPriority::Highest);
}
}
}
#if WITH_EDITOR
// Call the finish compilation outside of the critical section since those compilations
// might need to register new distance field tasks which also uses the critical section.
if (RequiredFinishCompilation.Num())
{
FStaticMeshCompilingManager::Get().FinishCompilation(RequiredFinishCompilation.Array());
}
#endif
if (bReferenced)
{
if (!bHadToBlock)
{
StartTime = FPlatformTime::Seconds();
}
bHadToBlock = true;
FPlatformProcess::Sleep(.01f);
}
}
while (bReferenced);
if (bHadToBlock &&
bWarnIfBlocked
#if WITH_EDITOR
&& !FAutomationTestFramework::Get().GetCurrentTest() // HACK - Don't output this warning during automation test
#endif
)
{
UE_LOG(LogStaticMesh, Display, TEXT("Main thread blocked for %.3fs for async distance field build of %s to complete! This can happen if the mesh is rebuilt excessively."),
(float)(FPlatformTime::Seconds() - StartTime),
*StaticMesh->GetName());
}
}
void FDistanceFieldAsyncQueue2::BlockUntilAllBuildsComplete()
{
TRACE_CPUPROFILER_EVENT_SCOPE(FDistanceFieldAsyncQueue2::BlockUntilAllBuildsComplete)
do
{
#if WITH_EDITOR
FStaticMeshCompilingManager::Get().FinishAllCompilation();
#endif
// Reschedule as highest prio since we're explicitly waiting on them
{
FScopeLock Lock(&CriticalSection);
for (int TaskIndex = 0; TaskIndex < ReferencedTasks.Num(); TaskIndex++)
{
RescheduleBackgroundTask(ReferencedTasks[TaskIndex], EQueuedWorkPriority::Highest);
}
}
ProcessAsyncTasks();
FPlatformProcess::Sleep(.01f);
}
while (GetNumOutstandingTasks() > 0);
}
void FDistanceFieldAsyncQueue2::Build(FAsyncDistanceFieldTask2* Task, FQueuedThreadPool& BuildThreadPool)
{
//#if WITH_EDITOR
// Editor 'force delete' can null any UObject pointers which are seen by reference collecting (eg FProperty or serialized)
if (Task->StaticMesh && Task->GenerateSource)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FDistanceFieldAsyncQueue2::Build);
const FStaticMeshLODResources& LODModel = Task->GenerateSource->GetRenderData()->LODResources[0];
MeshUtilities->GenerateSignedDistanceFieldVolumeData(
Task->StaticMesh->GetName(),
Task->SourceMeshData,
LODModel,
BuildThreadPool,
Task->MaterialBlendModes,
Task->GenerateSource->GetRenderData()->Bounds,
Task->DistanceFieldResolutionScale,
Task->bGenerateDistanceFieldAsIfTwoSided,
*Task->GeneratedVolumeData);
}
CompletedTasks.Push(Task);
//#endif
}
void FDistanceFieldAsyncQueue2::AddReferencedObjects(FReferenceCollector& Collector)
{
FScopeLock Lock(&CriticalSection);
for (int TaskIndex = 0; TaskIndex < ReferencedTasks.Num(); TaskIndex++)
{
// Make sure none of the UObjects referenced by the async tasks are GC'ed during the task
Collector.AddReferencedObject(ReferencedTasks[TaskIndex]->StaticMesh);
Collector.AddReferencedObject(ReferencedTasks[TaskIndex]->GenerateSource);
}
}
FString FDistanceFieldAsyncQueue2::GetReferencerName() const
{
return TEXT("FDistanceFieldAsyncQueue2");
}
void FDistanceFieldAsyncQueue2::ProcessAsyncTasks(bool bLimitExecutionTime)
{
// #if WITH_EDITOR
TRACE_CPUPROFILER_EVENT_SCOPE(FDistanceFieldAsyncQueue2::ProcessAsyncTasks);
ProcessPendingTasks();
FObjectCacheContextScope ObjectCacheScope;
const double MaxProcessingTime = 0.016f;
double StartTime = FPlatformTime::Seconds();
while (!bLimitExecutionTime || (FPlatformTime::Seconds() - StartTime) < MaxProcessingTime)
{
FAsyncDistanceFieldTask2* Task = CompletedTasks.Pop();
if (Task == nullptr)
{
break;
}
// We want to count each resource built from a DDC miss, so count each iteration of the loop separately.
COOK_STAT(auto Timer = DistanceFieldCookStats::UsageStats.TimeSyncWork());
bool bWasCancelled = false;
{
FScopeLock Lock(&CriticalSection);
bWasCancelled = ReferencedTasks.Remove(Task) == 0;
}
if (bWasCancelled)
{
continue;
}
if (Task->AsyncTask)
{
Task->AsyncTask->EnsureCompletion();
Task->AsyncTask.Reset();
}
// Editor 'force delete' can null any UObject pointers which are seen by reference collecting (eg FProperty or serialized)
if (Task->StaticMesh)
{
Task->GeneratedVolumeData->bAsyncBuilding = false;
FDistanceFieldVolumeData* OldVolumeData = Task->StaticMesh->GetRenderData()->LODResources[0].DistanceFieldData;
// Assign the new volume data, this is safe because the render thread makes a copy of the pointer at scene proxy creation time.
Task->StaticMesh->GetRenderData()->LODResources[0].DistanceFieldData = Task->GeneratedVolumeData;
// Renderstates are not initialized between UStaticMesh::PreEditChange() and UStaticMesh::PostEditChange()
if (Task->StaticMesh->GetRenderData()->IsInitialized())
{
for (UStaticMeshComponent* Component : ObjectCacheScope.GetContext().GetStaticMeshComponents(Task->StaticMesh))
{
if (Component->IsRegistered() && Component->IsRenderStateCreated())
{
Component->MarkRenderStateDirty();
}
}
}
if (OldVolumeData)
{
// Rendering thread may still be referencing the old one, use the deferred cleanup interface to delete it next frame when it is safe
BeginCleanup(OldVolumeData);
}
//第二阶段
BuildMeshCardRepresentation(Task->StaticMesh, *Task->StaticMesh->GetRenderData(), &Task->SourceMeshData);
}
delete Task;
}
// #endif
}
void FDistanceFieldAsyncQueue2::Shutdown()
{
CancelAllOutstandingBuilds();
UE_LOG(LogStaticMesh, Log, TEXT("Abandoning remaining async distance field tasks for shutdown"));
ThreadPool->Destroy();
}

View File

@@ -0,0 +1,456 @@
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
MeshCardRepresentation.cpp
=============================================================================*/
#include "MeshCardRepresentation2.h"
#include "MeshCardRepresentation.h"
#include "MeshUtilities2.h"
#include "Modules/ModuleManager.h"
#include "StaticMeshResources.h"
#include "ProfilingDebugging/CookStats.h"
#include "Templates/UniquePtr.h"
#include "Engine/StaticMesh.h"
#include "Misc/AutomationTest.h"
#include "Misc/QueuedThreadPoolWrapper.h"
#include "ObjectCacheContext.h"
#if WITH_EDITOR
#include "AssetCompilingManager.h"
// #include "DerivedDataCacheInterface.h"
// #include "MeshUtilities.h"
// #include "StaticMeshCompiler.h"
#endif
#if WITH_EDITORONLY_DATA
#include "IMeshBuilderModule.h"
#endif
#if ENABLE_COOK_STATS
namespace CardRepresentationCookStats
{
FCookStats::FDDCResourceUsageStats UsageStats;
static FCookStatsManager::FAutoRegisterCallback RegisterCookStats([](FCookStatsManager::AddStatFuncRef AddStat)
{
UsageStats.LogStats(AddStat, TEXT("CardRepresentation.Usage"), TEXT(""));
});
}
#endif
// static TAutoConsoleVariable<int32> CVarCardRepresentation(
// TEXT("r.MeshCardRepresentation"),
// 1,
// TEXT(""),
// ECVF_ReadOnly);
// static TAutoConsoleVariable<float> CVarCardRepresentationMinSurface(
// TEXT("r.MeshCardRepresentation.MinSurface"),
// 0.2f,
// TEXT("Min percentage of surface treshold to spawn a new card, [0;1] range."),
// ECVF_ReadOnly);
FCardRepresentationAsyncQueue2* GCardRepresentationAsyncQueue2 = NULL;
int32 GUseAsyncCardRepresentationBuildQueue2 = 1;
static FAutoConsoleVariableRef CVarCardRepresentationAsyncBuildQueue2(
TEXT("r.MeshCardRepresentation.Async"),
GUseAsyncCardRepresentationBuildQueue2,
TEXT("."),
ECVF_Default | ECVF_ReadOnly
);
FCardRepresentationAsyncQueue2::FCardRepresentationAsyncQueue2()
{
MeshUtilities = NULL;
#if WITH_EDITOR
const int32 MaxConcurrency = -1;
// In Editor, we allow faster compilation by letting the asset compiler's scheduler organize work.
ThreadPool = MakeUnique<FQueuedThreadPoolWrapper>(FAssetCompilingManager::Get().GetThreadPool(), MaxConcurrency, [](EQueuedWorkPriority) { return EQueuedWorkPriority::Lowest; });
#else
const int32 MaxConcurrency = -1;
ThreadPool = MakeUnique<FQueuedThreadPoolWrapper>(GThreadPool, MaxConcurrency, [](EQueuedWorkPriority) { return EQueuedWorkPriority::Lowest; });
#endif
}
FCardRepresentationAsyncQueue2::~FCardRepresentationAsyncQueue2()
{
}
void FAsyncCardRepresentationTaskWorker2::DoWork()
{
// Put on background thread to avoid interfering with game-thread bound tasks
FQueuedThreadPoolTaskGraphWrapper TaskGraphWrapper(ENamedThreads::AnyBackgroundThreadNormalTask);
GCardRepresentationAsyncQueue2->Build(&Task, TaskGraphWrapper);
}
void FCardRepresentationAsyncQueue2::CancelBackgroundTask(TArray<FAsyncCardRepresentationTask2*> Tasks)
{
// Do all the cancellation first to make sure none of these tasks
// get scheduled as we're waiting for completion.
for (FAsyncCardRepresentationTask2* Task : Tasks)
{
if (Task->AsyncTask)
{
Task->AsyncTask->Cancel();
}
}
for (FAsyncCardRepresentationTask2* Task : Tasks)
{
if (Task->AsyncTask)
{
Task->AsyncTask->EnsureCompletion();
Task->AsyncTask.Reset();
}
}
}
void FCardRepresentationAsyncQueue2::StartBackgroundTask(FAsyncCardRepresentationTask2* Task)
{
check(Task->AsyncTask == nullptr);
Task->AsyncTask = MakeUnique<FAsyncTask<FAsyncCardRepresentationTaskWorker2>>(*Task);
Task->AsyncTask->StartBackgroundTask(ThreadPool.Get(), EQueuedWorkPriority::Lowest);
}
void FCardRepresentationAsyncQueue2::ProcessPendingTasks()
{
FScopeLock Lock(&CriticalSection);
TArray<FAsyncCardRepresentationTask2*> Tasks = MoveTemp(PendingTasks);
for (FAsyncCardRepresentationTask2* Task : Tasks)
{
if (Task->GenerateSource && Task->GenerateSource->IsCompiling())
{
PendingTasks.Add(Task);
}
else
{
StartBackgroundTask(Task);
}
}
}
void FCardRepresentationAsyncQueue2::AddTask(FAsyncCardRepresentationTask2* Task)
{
// #if WITH_EDITOR
if (!MeshUtilities)
{
MeshUtilities = &FModuleManager::Get().LoadModuleChecked<IMeshUtilities2>(TEXT("MeshUtilities2"));
}
{
// Array protection when called from multiple threads
FScopeLock Lock(&CriticalSection);
ReferencedTasks.Add(Task);
}
// The Source Mesh's RenderData is not yet ready, postpone the build
if (Task->GenerateSource->IsCompiling())
{
// Array protection when called from multiple threads
FScopeLock Lock(&CriticalSection);
PendingTasks.Add(Task);
}
else
{
// If we're already in worker threads there is no need to launch an async task.
if (GUseAsyncCardRepresentationBuildQueue2 || !IsInGameThread())
{
StartBackgroundTask(Task);
}
else
{
// To avoid deadlocks, we must queue the inner build tasks on another thread pool, so use the task graph.
// Put on background thread to avoid interfering with game-thread bound tasks
FQueuedThreadPoolTaskGraphWrapper TaskGraphWrapper(ENamedThreads::AnyBackgroundThreadNormalTask);
Build(Task, TaskGraphWrapper);
}
}
// #else
// UE_LOG(LogStaticMesh,Fatal,TEXT("Tried to build a card representation without editor support (this should have been done during cooking)"));
// #endif
}
void FCardRepresentationAsyncQueue2::CancelBuild(UStaticMesh* StaticMesh)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FCardRepresentationAsyncQueue2::CancelBuild);
TArray<FAsyncCardRepresentationTask2*> TasksToCancel;
{
FScopeLock Lock(&CriticalSection);
TArray<FAsyncCardRepresentationTask2*> Tasks = MoveTemp(PendingTasks);
PendingTasks.Reserve(Tasks.Num());
for (FAsyncCardRepresentationTask2* Task : Tasks)
{
if (Task->GenerateSource != StaticMesh && Task->StaticMesh != StaticMesh)
{
PendingTasks.Add(Task);
}
}
Tasks = MoveTemp(ReferencedTasks);
ReferencedTasks.Reserve(Tasks.Num());
for (FAsyncCardRepresentationTask2* Task : Tasks)
{
if (Task->GenerateSource != StaticMesh && Task->StaticMesh != StaticMesh)
{
ReferencedTasks.Add(Task);
}
else
{
TasksToCancel.Add(Task);
}
}
}
CancelBackgroundTask(TasksToCancel);
for (FAsyncCardRepresentationTask2* Task : TasksToCancel)
{
if (Task->GeneratedCardRepresentation != nullptr)
{
// Rendering thread may still be referencing the old one, use the deferred cleanup interface to delete it next frame when it is safe
BeginCleanup(Task->GeneratedCardRepresentation);
}
delete Task;
}
}
void FCardRepresentationAsyncQueue2::CancelAllOutstandingBuilds()
{
TRACE_CPUPROFILER_EVENT_SCOPE(FCardRepresentationAsyncQueue2::CancelAllOutstandingBuilds);
TArray<FAsyncCardRepresentationTask2*> OutstandingTasks;
{
FScopeLock Lock(&CriticalSection);
PendingTasks.Empty();
OutstandingTasks = MoveTemp(ReferencedTasks);
}
CancelBackgroundTask(OutstandingTasks);
for (FAsyncCardRepresentationTask2* Task : OutstandingTasks)
{
delete Task;
}
}
void FCardRepresentationAsyncQueue2::RescheduleBackgroundTask(FAsyncCardRepresentationTask2* InTask, EQueuedWorkPriority InPriority)
{
if (InTask->AsyncTask)
{
if (InTask->AsyncTask->GetPriority() != InPriority)
{
InTask->AsyncTask->Reschedule(GThreadPool, InPriority);
}
}
}
void FCardRepresentationAsyncQueue2::BlockUntilBuildComplete(UStaticMesh* StaticMesh, bool bWarnIfBlocked)
{
// We will track the wait time here, but only the cycles used.
// This function is called whether or not an async task is pending,
// so we have to look elsewhere to properly count how many resources have actually finished building.
COOK_STAT(auto Timer = CardRepresentationCookStats::UsageStats.TimeAsyncWait());
COOK_STAT(Timer.TrackCyclesOnly());
bool bReferenced = false;
bool bHadToBlock = false;
double StartTime = 0;
// #if WITH_EDITOR
// FStaticMeshCompilingManager::Get().FinishCompilation({ StaticMesh });
// #endif
do
{
ProcessAsyncTasks();
bReferenced = false;
{
FScopeLock Lock(&CriticalSection);
for (int TaskIndex = 0; TaskIndex < ReferencedTasks.Num(); TaskIndex++)
{
if (ReferencedTasks[TaskIndex]->StaticMesh == StaticMesh ||
ReferencedTasks[TaskIndex]->GenerateSource == StaticMesh)
{
bReferenced = true;
RescheduleBackgroundTask(ReferencedTasks[TaskIndex], EQueuedWorkPriority::Highest);
}
}
}
if (bReferenced)
{
if (!bHadToBlock)
{
StartTime = FPlatformTime::Seconds();
}
bHadToBlock = true;
FPlatformProcess::Sleep(.01f);
}
}
while (bReferenced);
if (bHadToBlock &&
bWarnIfBlocked
#if WITH_EDITOR
&& !FAutomationTestFramework::Get().GetCurrentTest() // HACK - Don't output this warning during automation test
#endif
)
{
UE_LOG(LogStaticMesh, Display, TEXT("Main thread blocked for %.3fs for async card representation build of %s to complete! This can happen if the mesh is rebuilt excessively."),
(float)(FPlatformTime::Seconds() - StartTime),
*StaticMesh->GetName());
}
}
void FCardRepresentationAsyncQueue2::BlockUntilAllBuildsComplete()
{
TRACE_CPUPROFILER_EVENT_SCOPE(FCardRepresentationAsyncQueue2::BlockUntilAllBuildsComplete)
do
{
// #if WITH_EDITOR
// FStaticMeshCompilingManager::Get().FinishAllCompilation();
// #endif
// Reschedule as highest prio since we're explicitly waiting on them
{
FScopeLock Lock(&CriticalSection);
for (int TaskIndex = 0; TaskIndex < ReferencedTasks.Num(); TaskIndex++)
{
RescheduleBackgroundTask(ReferencedTasks[TaskIndex], EQueuedWorkPriority::Highest);
}
}
ProcessAsyncTasks();
FPlatformProcess::Sleep(.01f);
}
while (GetNumOutstandingTasks() > 0);
}
void FCardRepresentationAsyncQueue2::Build(FAsyncCardRepresentationTask2* Task, FQueuedThreadPool& BuildThreadPool)
{
// #if WITH_EDITOR
// Editor 'force delete' can null any UObject pointers which are seen by reference collecting (eg UProperty or serialized)
if (Task->StaticMesh && Task->GenerateSource)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FCardRepresentationAsyncQueue2::Build);
const FStaticMeshLODResources& LODModel = Task->GenerateSource->GetRenderData()->LODResources[0];
Task->bSuccess = MeshUtilities->GenerateCardRepresentationData(
Task->StaticMesh->GetName(),
Task->SourceMeshData,
LODModel,
BuildThreadPool,
Task->MaterialBlendModes,
Task->GenerateSource->GetRenderData()->Bounds,
Task->GenerateSource->GetRenderData()->LODResources[0].DistanceFieldData,
Task->bGenerateDistanceFieldAsIfTwoSided,
*Task->GeneratedCardRepresentation);
}
CompletedTasks.Push(Task);
// #endif
}
void FCardRepresentationAsyncQueue2::AddReferencedObjects(FReferenceCollector& Collector)
{
FScopeLock Lock(&CriticalSection);
for (int TaskIndex = 0; TaskIndex < ReferencedTasks.Num(); TaskIndex++)
{
// Make sure none of the UObjects referenced by the async tasks are GC'ed during the task
Collector.AddReferencedObject(ReferencedTasks[TaskIndex]->StaticMesh);
Collector.AddReferencedObject(ReferencedTasks[TaskIndex]->GenerateSource);
}
}
FString FCardRepresentationAsyncQueue2::GetReferencerName() const
{
return TEXT("FCardRepresentationAsyncQueue2");
}
void FCardRepresentationAsyncQueue2::ProcessAsyncTasks(bool bLimitExecutionTime)
{
// #if WITH_EDITOR
TRACE_CPUPROFILER_EVENT_SCOPE(FCardRepresentationAsyncQueue2::ProcessAsyncTasks);
ProcessPendingTasks();
FObjectCacheContextScope ObjectCacheScope;
const double MaxProcessingTime = 0.016f;
double StartTime = FPlatformTime::Seconds();
while (!bLimitExecutionTime || (FPlatformTime::Seconds() - StartTime) < MaxProcessingTime)
{
FAsyncCardRepresentationTask2* Task = CompletedTasks.Pop();
if (Task == nullptr)
{
break;
}
// We want to count each resource built from a DDC miss, so count each iteration of the loop separately.
COOK_STAT(auto Timer = CardRepresentationCookStats::UsageStats.TimeSyncWork());
bool bWasCancelled = false;
{
FScopeLock Lock(&CriticalSection);
bWasCancelled = ReferencedTasks.Remove(Task) == 0;
}
if (bWasCancelled)
{
continue;
}
if (Task->AsyncTask)
{
Task->AsyncTask->EnsureCompletion();
Task->AsyncTask.Reset();
}
// Editor 'force delete' can null any UObject pointers which are seen by reference collecting (eg UProperty or serialized)
if (Task->StaticMesh && Task->bSuccess)
{
FCardRepresentationData* OldCardData = Task->StaticMesh->GetRenderData()->LODResources[0].CardRepresentationData;
// Assign the new data, this is safe because the render threads makes a copy of the pointer at scene proxy creation time.
Task->StaticMesh->GetRenderData()->LODResources[0].CardRepresentationData = Task->GeneratedCardRepresentation;
// Any already created render state needs to be dirtied
if (Task->StaticMesh->GetRenderData()->IsInitialized())
{
for (UStaticMeshComponent* Component : ObjectCacheScope.GetContext().GetStaticMeshComponents(Task->StaticMesh))
{
if (Component->IsRegistered() && Component->IsRenderStateCreated())
{
Component->MarkRenderStateDirty();
}
}
}
// Rendering thread may still be referencing the old one, use the deferred cleanup interface to delete it next frame when it is safe
BeginCleanup(OldCardData);
// {
// TArray<uint8> DerivedData;
// // Save built data to DDC
// FMemoryWriter Ar(DerivedData, /*bIsPersistent=*/ true);
// Ar << *(Task->StaticMesh->GetRenderData()->LODResources[0].CardRepresentationData);
// GetDerivedDataCacheRef().Put(*Task->DDCKey, DerivedData, Task->StaticMesh->GetPathName());
// COOK_STAT(Timer.AddMiss(DerivedData.Num()));
// }
}
delete Task;
}
// #endif
}
void FCardRepresentationAsyncQueue2::Shutdown()
{
CancelAllOutstandingBuilds();
UE_LOG(LogStaticMesh, Log, TEXT("Abandoning remaining async card representation tasks for shutdown"));
ThreadPool->Destroy();
}

View File

@@ -0,0 +1,510 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "MeshUtilities2/Public/MeshUtilities2.h"
#include "MeshUtilitiesPrivate.h"
#include "StaticMeshResources.h"
#include "MeshCardRepresentation.h"
#include "DistanceFieldAtlas.h"
#include "MeshRepresentationCommon.h"
class FGenerateCardMeshContext
{
public:
const FString& MeshName;
RTCScene FullMeshEmbreeScene;
RTCDevice EmbreeDevice;
FCardRepresentationData& OutData;
FGenerateCardMeshContext(const FString& InMeshName, RTCScene InEmbreeScene, RTCDevice InEmbreeDevice, FCardRepresentationData& InOutData) :
MeshName(InMeshName),
FullMeshEmbreeScene(InEmbreeScene),
EmbreeDevice(InEmbreeDevice),
OutData(InOutData)
{}
};
class FPlacedCard
{
public:
int32 SliceMin;
int32 SliceMax;
float NearPlane;
float FarPlane;
FBox Bounds;
int32 NumHits;
};
#if USE_EMBREE
bool IsSurfacePointInsideMesh(const RTCScene& FullMeshEmbreeScene, FVector SurfacePoint, FVector SurfaceNormal, const TArray<FVector4>& RayDirectionsOverHemisphere)
{
uint32 NumHits = 0;
uint32 NumBackFaceHits = 0;
const FMatrix SurfaceBasis = MeshRepresentation::GetTangentBasisFrisvad(SurfaceNormal);
for (int32 SampleIndex = 0; SampleIndex < RayDirectionsOverHemisphere.Num(); ++SampleIndex)
{
FVector RayDirection = SurfaceBasis.TransformVector(RayDirectionsOverHemisphere[SampleIndex]);
FEmbreeRay EmbreeRay;
EmbreeRay.ray.org_x = SurfacePoint.X;
EmbreeRay.ray.org_y = SurfacePoint.Y;
EmbreeRay.ray.org_z = SurfacePoint.Z;
EmbreeRay.ray.dir_x = RayDirection.X;
EmbreeRay.ray.dir_y = RayDirection.Y;
EmbreeRay.ray.dir_z = RayDirection.Z;
EmbreeRay.ray.tnear = 0.1f;
EmbreeRay.ray.tfar = FLT_MAX;
FEmbreeIntersectionContext EmbreeContext;
rtcInitIntersectContext(&EmbreeContext);
rtcIntersect1(FullMeshEmbreeScene, &EmbreeContext, &EmbreeRay);
if (EmbreeRay.hit.geomID != RTC_INVALID_GEOMETRY_ID && EmbreeRay.hit.primID != RTC_INVALID_GEOMETRY_ID)
{
++NumHits;
if (FVector::DotProduct(RayDirection, EmbreeRay.GetHitNormal()) > 0.0f && !EmbreeContext.IsHitTwoSided())
{
++NumBackFaceHits;
}
}
}
if (NumHits > 0 && NumBackFaceHits > RayDirectionsOverHemisphere.Num() * 0.4f)
{
return true;
}
return false;
}
struct FSurfacePoint
{
float MinT;
float HitT;
};
int32 UpdatePlacedCards(TArray<FPlacedCard, TInlineAllocator<16>>& PlacedCards,
FVector RayOriginFrame,
FVector RayDirection,
FVector HeighfieldStepX,
FVector HeighfieldStepY,
FIntPoint HeighfieldSize,
int32 MeshSliceNum,
float MaxRayT,
int32 MinCardHits,
FVector VoxelExtent,
const TArray<TArray<FSurfacePoint, TInlineAllocator<16>>>& HeightfieldLayers)
{
for (int32 PlacedCardIndex = 0; PlacedCardIndex < PlacedCards.Num(); ++PlacedCardIndex)
{
FPlacedCard& PlacedCard = PlacedCards[PlacedCardIndex];
PlacedCard.NearPlane = PlacedCard.SliceMin / float(MeshSliceNum) * MaxRayT;
PlacedCard.FarPlane = (PlacedCard.SliceMax / float(MeshSliceNum)) * MaxRayT;
PlacedCard.Bounds.Init();
PlacedCard.NumHits = 0;
}
for (int32 HeighfieldY = 0; HeighfieldY < HeighfieldSize.Y; ++HeighfieldY)
{
for (int32 HeighfieldX = 0; HeighfieldX < HeighfieldSize.X; ++HeighfieldX)
{
const int32 HeightfieldLinearIndex = HeighfieldX + HeighfieldY * HeighfieldSize.X;
FVector RayOrigin = RayOriginFrame;
RayOrigin += (HeighfieldX + 0.5f) * HeighfieldStepX;
RayOrigin += (HeighfieldY + 0.5f) * HeighfieldStepY;
int32 LayerIndex = 0;
int32 PlacedCardIndex = 0;
while (LayerIndex < HeightfieldLayers[HeightfieldLinearIndex].Num() && PlacedCardIndex < PlacedCards.Num())
{
const FSurfacePoint& SurfacePoint = HeightfieldLayers[HeightfieldLinearIndex][LayerIndex];
FPlacedCard& PlacedCard = PlacedCards[PlacedCardIndex];
if (SurfacePoint.HitT >= PlacedCard.NearPlane && SurfacePoint.HitT <= PlacedCard.FarPlane
&& SurfacePoint.MinT <= PlacedCard.NearPlane)
{
PlacedCard.NumHits += 1;
PlacedCard.Bounds += RayOrigin + SurfacePoint.HitT * RayDirection - VoxelExtent;
PlacedCard.Bounds += RayOrigin + SurfacePoint.HitT * RayDirection + VoxelExtent;
++PlacedCardIndex;
++LayerIndex;
}
else
{
if (SurfacePoint.HitT >= PlacedCard.FarPlane)
{
++PlacedCardIndex;
}
else
{
++LayerIndex;
}
}
}
}
}
int32 NumMeshHits = 0;
for (int32 PlacedCardIndex = 0; PlacedCardIndex < PlacedCards.Num(); ++PlacedCardIndex)
{
const FPlacedCard& PlacedCard = PlacedCards[PlacedCardIndex];
if (PlacedCard.NumHits >= MinCardHits)
{
NumMeshHits += PlacedCard.NumHits;
}
}
return NumMeshHits;
}
void SerializePlacedCards(TArray<FPlacedCard, TInlineAllocator<16>>& PlacedCards,
int32 LODLevel,
int32 Orientation,
int32 MinCardHits,
const FBox& MeshCardsBounds,
FCardRepresentationData& OutData)
{
for (int32 PlacedCardIndex = 0; PlacedCardIndex < PlacedCards.Num(); ++PlacedCardIndex)
{
const FPlacedCard& PlacedCard = PlacedCards[PlacedCardIndex];
if (PlacedCard.NumHits >= MinCardHits)
{
const FBox ClampedBox = PlacedCard.Bounds.Overlap(MeshCardsBounds);
FLumenCardBuildData CardBuildData;
CardBuildData.Center = ClampedBox.GetCenter();
CardBuildData.Extent = ClampedBox.GetExtent();
CardBuildData.Extent = FLumenCardBuildData::TransformFaceExtent(CardBuildData.Extent, Orientation);
CardBuildData.Orientation = Orientation;
CardBuildData.LODLevel = LODLevel;
OutData.MeshCardsBuildData.CardBuildData.Add(CardBuildData);
}
}
}
void BuildMeshCards(const FBox& MeshBounds, const FGenerateCardMeshContext& Context, FCardRepresentationData& OutData)
{
static const auto CVarMeshCardRepresentationMinSurface = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.MeshCardRepresentation.MinSurface"));
const float MinSurfaceThreshold = CVarMeshCardRepresentationMinSurface->GetValueOnAnyThread();
// Make sure BBox isn't empty and we can generate card representation for it. This handles e.g. infinitely thin planes.
const FVector MeshCardsBoundsCenter = MeshBounds.GetCenter();
const FVector MeshCardsBoundsExtent = FVector::Max(MeshBounds.GetExtent() + 1.0f, FVector(5.0f));
const FBox MeshCardsBounds(MeshCardsBoundsCenter - MeshCardsBoundsExtent, MeshCardsBoundsCenter + MeshCardsBoundsExtent);
OutData.MeshCardsBuildData.Bounds = MeshCardsBounds;
OutData.MeshCardsBuildData.MaxLODLevel = 1;
OutData.MeshCardsBuildData.CardBuildData.Reset();
const float SamplesPerWorldUnit = 1.0f / 10.0f;
const int32 MinSamplesPerAxis = 4;
const int32 MaxSamplesPerAxis = 64;
FIntVector VolumeSizeInVoxels;
VolumeSizeInVoxels.X = FMath::Clamp<int32>(MeshCardsBounds.GetSize().X * SamplesPerWorldUnit, MinSamplesPerAxis, MaxSamplesPerAxis);
VolumeSizeInVoxels.Y = FMath::Clamp<int32>(MeshCardsBounds.GetSize().Y * SamplesPerWorldUnit, MinSamplesPerAxis, MaxSamplesPerAxis);
VolumeSizeInVoxels.Z = FMath::Clamp<int32>(MeshCardsBounds.GetSize().Z * SamplesPerWorldUnit, MinSamplesPerAxis, MaxSamplesPerAxis);
const FVector VoxelExtent = MeshCardsBounds.GetSize() / FVector(VolumeSizeInVoxels);
// Generate random ray directions over a hemisphere
TArray<FVector4> RayDirectionsOverHemisphere;
{
FRandomStream RandomStream(0);
MeshUtilities::GenerateStratifiedUniformHemisphereSamples(64, RandomStream, RayDirectionsOverHemisphere);
}
for (int32 Orientation = 0; Orientation < 6; ++Orientation)
{
FIntPoint HeighfieldSize(0, 0);
FVector RayDirection(0.0f, 0.0f, 0.0f);
FVector RayOriginFrame = MeshCardsBounds.Min;
FVector HeighfieldStepX(0.0f, 0.0f, 0.0f);
FVector HeighfieldStepY(0.0f, 0.0f, 0.0f);
float MaxRayT = 0.0f;
int32 MeshSliceNum = 0;
switch (Orientation / 2)
{
case 0:
MaxRayT = MeshCardsBounds.GetSize().X + 0.1f;
MeshSliceNum = VolumeSizeInVoxels.X;
HeighfieldSize.X = VolumeSizeInVoxels.Y;
HeighfieldSize.Y = VolumeSizeInVoxels.Z;
HeighfieldStepX = FVector(0.0f, MeshCardsBounds.GetSize().Y / HeighfieldSize.X, 0.0f);
HeighfieldStepY = FVector(0.0f, 0.0f, MeshCardsBounds.GetSize().Z / HeighfieldSize.Y);
break;
case 1:
MaxRayT = MeshCardsBounds.GetSize().Y + 0.1f;
MeshSliceNum = VolumeSizeInVoxels.Y;
HeighfieldSize.X = VolumeSizeInVoxels.X;
HeighfieldSize.Y = VolumeSizeInVoxels.Z;
HeighfieldStepX = FVector(MeshCardsBounds.GetSize().X / HeighfieldSize.X, 0.0f, 0.0f);
HeighfieldStepY = FVector(0.0f, 0.0f, MeshCardsBounds.GetSize().Z / HeighfieldSize.Y);
break;
case 2:
MaxRayT = MeshCardsBounds.GetSize().Z + 0.1f;
MeshSliceNum = VolumeSizeInVoxels.Z;
HeighfieldSize.X = VolumeSizeInVoxels.X;
HeighfieldSize.Y = VolumeSizeInVoxels.Y;
HeighfieldStepX = FVector(MeshCardsBounds.GetSize().X / HeighfieldSize.X, 0.0f, 0.0f);
HeighfieldStepY = FVector(0.0f, MeshCardsBounds.GetSize().Y / HeighfieldSize.Y, 0.0f);
break;
}
switch (Orientation)
{
case 0:
RayDirection.X = +1.0f;
break;
case 1:
RayDirection.X = -1.0f;
RayOriginFrame.X = MeshCardsBounds.Max.X;
break;
case 2:
RayDirection.Y = +1.0f;
break;
case 3:
RayDirection.Y = -1.0f;
RayOriginFrame.Y = MeshCardsBounds.Max.Y;
break;
case 4:
RayDirection.Z = +1.0f;
break;
case 5:
RayDirection.Z = -1.0f;
RayOriginFrame.Z = MeshCardsBounds.Max.Z;
break;
default:
check(false);
};
TArray<TArray<FSurfacePoint, TInlineAllocator<16>>> HeightfieldLayers;
HeightfieldLayers.SetNum(HeighfieldSize.X * HeighfieldSize.Y);
// Fill surface points
{
TRACE_CPUPROFILER_EVENT_SCOPE(FillSurfacePoints);
TArray<float> Heightfield;
Heightfield.SetNum(HeighfieldSize.X * HeighfieldSize.Y);
for (int32 HeighfieldY = 0; HeighfieldY < HeighfieldSize.Y; ++HeighfieldY)
{
for (int32 HeighfieldX = 0; HeighfieldX < HeighfieldSize.X; ++HeighfieldX)
{
Heightfield[HeighfieldX + HeighfieldY * HeighfieldSize.X] = -1.0f;
}
}
for (int32 HeighfieldY = 0; HeighfieldY < HeighfieldSize.Y; ++HeighfieldY)
{
for (int32 HeighfieldX = 0; HeighfieldX < HeighfieldSize.X; ++HeighfieldX)
{
FVector RayOrigin = RayOriginFrame;
RayOrigin += (HeighfieldX + 0.5f) * HeighfieldStepX;
RayOrigin += (HeighfieldY + 0.5f) * HeighfieldStepY;
float StepTMin = 0.0f;
for (int32 StepIndex = 0; StepIndex < 64; ++StepIndex)
{
FEmbreeRay EmbreeRay;
EmbreeRay.ray.org_x = RayOrigin.X;
EmbreeRay.ray.org_y = RayOrigin.Y;
EmbreeRay.ray.org_z = RayOrigin.Z;
EmbreeRay.ray.dir_x = RayDirection.X;
EmbreeRay.ray.dir_y = RayDirection.Y;
EmbreeRay.ray.dir_z = RayDirection.Z;
EmbreeRay.ray.tnear = StepTMin;
EmbreeRay.ray.tfar = FLT_MAX;
FEmbreeIntersectionContext EmbreeContext;
rtcInitIntersectContext(&EmbreeContext);
rtcIntersect1(Context.FullMeshEmbreeScene, &EmbreeContext, &EmbreeRay);
if (EmbreeRay.hit.geomID != RTC_INVALID_GEOMETRY_ID && EmbreeRay.hit.primID != RTC_INVALID_GEOMETRY_ID)
{
const FVector SurfacePoint = RayOrigin + RayDirection * EmbreeRay.ray.tfar;
const FVector SurfaceNormal = EmbreeRay.GetHitNormal();
const float NdotD = FVector::DotProduct(RayDirection, SurfaceNormal);
const bool bPassCullTest = EmbreeContext.IsHitTwoSided() || NdotD <= 0.0f;
const bool bPassProjectionAngleTest = FMath::Abs(NdotD) >= FMath::Cos(75.0f * (PI / 180.0f));
const float MinDistanceBetweenPoints = (MaxRayT / MeshSliceNum);
const bool bPassDistanceToAnotherSurfaceTest = EmbreeRay.ray.tnear <= 0.0f || (EmbreeRay.ray.tfar - EmbreeRay.ray.tnear > MinDistanceBetweenPoints);
if (bPassCullTest && bPassProjectionAngleTest && bPassDistanceToAnotherSurfaceTest)
{
const bool bIsInsideMesh = IsSurfacePointInsideMesh(Context.FullMeshEmbreeScene, SurfacePoint, SurfaceNormal, RayDirectionsOverHemisphere);
if (!bIsInsideMesh)
{
HeightfieldLayers[HeighfieldX + HeighfieldY * HeighfieldSize.X].Add(
{ EmbreeRay.ray.tnear, EmbreeRay.ray.tfar }
);
}
}
StepTMin = EmbreeRay.ray.tfar + 0.01f;
}
else
{
break;
}
}
}
}
}
const int32 MinCardHits = FMath::Floor(HeighfieldSize.X * HeighfieldSize.Y * MinSurfaceThreshold);
TArray<FPlacedCard, TInlineAllocator<16>> PlacedCards;
int32 PlacedCardsHits = 0;
// Place a default card
{
FPlacedCard PlacedCard;
PlacedCard.SliceMin = 0;
PlacedCard.SliceMax = MeshSliceNum;
PlacedCards.Add(PlacedCard);
PlacedCardsHits = UpdatePlacedCards(PlacedCards,
RayOriginFrame,
RayDirection,
HeighfieldStepX,
HeighfieldStepY,
HeighfieldSize,
MeshSliceNum,
MaxRayT,
MinCardHits,
VoxelExtent,
HeightfieldLayers);
if (PlacedCardsHits < MinCardHits)
{
PlacedCards.Reset();
}
}
SerializePlacedCards(PlacedCards, /*LOD level*/ 0, Orientation, MinCardHits, MeshCardsBounds, OutData);
// Try to place more cards by splitting existing ones
for (uint32 CardPlacementIteration = 0; CardPlacementIteration < 4; ++CardPlacementIteration)
{
TArray<FPlacedCard, TInlineAllocator<16>> BestPlacedCards;
int32 BestPlacedCardHits = PlacedCardsHits;
for (int32 PlacedCardIndex = 0; PlacedCardIndex < PlacedCards.Num(); ++PlacedCardIndex)
{
const FPlacedCard& PlacedCard = PlacedCards[PlacedCardIndex];
for (int32 SliceIndex = PlacedCard.SliceMin + 2; SliceIndex < PlacedCard.SliceMax; ++SliceIndex)
{
TArray<FPlacedCard, TInlineAllocator<16>> TempPlacedCards(PlacedCards);
FPlacedCard NewPlacedCard;
NewPlacedCard.SliceMin = SliceIndex;
NewPlacedCard.SliceMax = PlacedCard.SliceMax;
TempPlacedCards[PlacedCardIndex].SliceMax = SliceIndex - 1;
TempPlacedCards.Insert(NewPlacedCard, PlacedCardIndex + 1);
const int32 NumHits = UpdatePlacedCards(
TempPlacedCards,
RayOriginFrame,
RayDirection,
HeighfieldStepX,
HeighfieldStepY,
HeighfieldSize,
MeshSliceNum,
MaxRayT,
MinCardHits,
VoxelExtent,
HeightfieldLayers);
if (NumHits > BestPlacedCardHits)
{
BestPlacedCards = TempPlacedCards;
BestPlacedCardHits = NumHits;
}
}
}
if (BestPlacedCardHits >= PlacedCardsHits + MinCardHits)
{
PlacedCards = BestPlacedCards;
PlacedCardsHits = BestPlacedCardHits;
}
}
SerializePlacedCards(PlacedCards, /*LOD level*/ 1, Orientation, MinCardHits, MeshCardsBounds, OutData);
}
}
#endif // #if USE_EMBREE
bool FMeshUtilities2::GenerateCardRepresentationData(
FString MeshName,
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
const FStaticMeshLODResources& LODModel,
class FQueuedThreadPool& ThreadPool,
const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
const FBoxSphereBounds& Bounds,
const FDistanceFieldVolumeData* DistanceFieldVolumeData,
bool bGenerateAsIfTwoSided,
FCardRepresentationData& OutData)
{
#if USE_EMBREE
TRACE_CPUPROFILER_EVENT_SCOPE(FMeshUtilities2::GenerateCardRepresentationData);
const double StartTime = FPlatformTime::Seconds();
FEmbreeScene EmbreeScene;
MeshRepresentation::SetupEmbreeScene(MeshName,
SourceMeshData,
LODModel,
MaterialBlendModes,
bGenerateAsIfTwoSided,
EmbreeScene);
if (!EmbreeScene.EmbreeScene)
{
return false;
}
FGenerateCardMeshContext Context(MeshName, EmbreeScene.EmbreeScene, EmbreeScene.EmbreeDevice, OutData);
// Note: must operate on the SDF bounds because SDF generation can expand the mesh's bounds
BuildMeshCards(DistanceFieldVolumeData ? DistanceFieldVolumeData->LocalSpaceMeshBounds : Bounds.GetBox(), Context, OutData);
MeshRepresentation::DeleteEmbreeScene(EmbreeScene);
const float TimeElapsed = (float)(FPlatformTime::Seconds() - StartTime);
if (TimeElapsed > 1.0f)
{
UE_LOG(LogMeshUtilities, Log, TEXT("Finished mesh card build in %.1fs %s"),
TimeElapsed,
*MeshName);
}
return true;
#else
UE_LOG(LogMeshUtilities, Warning, TEXT("Platform did not set USE_EMBREE, GenerateCardRepresentationData failed."));
return false;
#endif
}

View File

@@ -0,0 +1,554 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "MeshUtilities2/Public/MeshUtilities2.h"
#include "MeshUtilitiesPrivate.h"
#include "RawMesh.h"
#include "StaticMeshResources.h"
#include "DistanceFieldAtlas.h"
#include "MeshRepresentationCommon.h"
#include "Async/ParallelFor.h"
#if USE_EMBREE
class FEmbreePointQueryContext : public RTCPointQueryContext
{
public:
RTCGeometry MeshGeometry;
int32 NumTriangles;
};
bool EmbreePointQueryFunction(RTCPointQueryFunctionArguments* args)
{
const FEmbreePointQueryContext* Context = (const FEmbreePointQueryContext*)args->context;
check(args->userPtr);
float& ClosestDistanceSq = *(float*)(args->userPtr);
const int32 TriangleIndex = args->primID;
check(TriangleIndex < Context->NumTriangles);
const FVector* VertexBuffer = (const FVector*)rtcGetGeometryBufferData(Context->MeshGeometry, RTC_BUFFER_TYPE_VERTEX, 0);
const uint32* IndexBuffer = (const uint32*)rtcGetGeometryBufferData(Context->MeshGeometry, RTC_BUFFER_TYPE_INDEX, 0);
const uint32 I0 = IndexBuffer[TriangleIndex * 3 + 0];
const uint32 I1 = IndexBuffer[TriangleIndex * 3 + 1];
const uint32 I2 = IndexBuffer[TriangleIndex * 3 + 2];
const FVector V0 = VertexBuffer[I0];
const FVector V1 = VertexBuffer[I1];
const FVector V2 = VertexBuffer[I2];
const FVector QueryPosition(args->query->x, args->query->y, args->query->z);
const FVector ClosestPoint = FMath::ClosestPointOnTriangleToPoint(QueryPosition, V0, V1, V2);
const float QueryDistanceSq = (ClosestPoint - QueryPosition).SizeSquared();
if (QueryDistanceSq < ClosestDistanceSq)
{
ClosestDistanceSq = QueryDistanceSq;
bool bShrinkQuery = true;
if (bShrinkQuery)
{
args->query->radius = FMath::Sqrt(ClosestDistanceSq);
// Return true to indicate that the query radius has shrunk
return true;
}
}
// Return false to indicate that the query radius hasn't changed
return false;
}
static int32 ComputeLinearVoxelIndex(FIntVector VoxelCoordinate, FIntVector VolumeDimensions)
{
return (VoxelCoordinate.Z * VolumeDimensions.Y + VoxelCoordinate.Y) * VolumeDimensions.X + VoxelCoordinate.X;
}
class FSparseMeshDistanceFieldAsyncTask
{
public:
FSparseMeshDistanceFieldAsyncTask(
const FEmbreeScene& InEmbreeScene,
const TArray<FVector4>* InSampleDirections,
float InLocalSpaceTraceDistance,
FBox InVolumeBounds,
float InLocalToVolumeScale,
FVector2D InDistanceFieldToVolumeScaleBias,
FIntVector InBrickCoordinate,
FIntVector InIndirectionSize,
bool bInUsePointQuery)
:
EmbreeScene(InEmbreeScene),
SampleDirections(InSampleDirections),
LocalSpaceTraceDistance(InLocalSpaceTraceDistance),
VolumeBounds(InVolumeBounds),
LocalToVolumeScale(InLocalToVolumeScale),
DistanceFieldToVolumeScaleBias(InDistanceFieldToVolumeScaleBias),
BrickCoordinate(InBrickCoordinate),
IndirectionSize(InIndirectionSize),
bUsePointQuery(bInUsePointQuery),
BrickMaxDistance(MIN_uint8),
BrickMinDistance(MAX_uint8)
{}
void DoWork();
// Readonly inputs
const FEmbreeScene& EmbreeScene;
const TArray<FVector4>* SampleDirections;
float LocalSpaceTraceDistance;
FBox VolumeBounds;
float LocalToVolumeScale;
FVector2D DistanceFieldToVolumeScaleBias;
FIntVector BrickCoordinate;
FIntVector IndirectionSize;
bool bUsePointQuery;
// Output
uint8 BrickMaxDistance;
uint8 BrickMinDistance;
TArray<uint8> DistanceFieldVolume;
};
int32 DebugX = 0;
int32 DebugY = 0;
int32 DebugZ = 0;
void FSparseMeshDistanceFieldAsyncTask::DoWork()
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSparseMeshDistanceFieldAsyncTask::DoWork);
const FVector IndirectionVoxelSize = VolumeBounds.GetSize() / FVector(IndirectionSize);
const FVector DistanceFieldVoxelSize = IndirectionVoxelSize / FVector(DistanceField::UniqueDataBrickSize);
const FVector BrickMinPosition = VolumeBounds.Min + FVector(BrickCoordinate) * IndirectionVoxelSize;
DistanceFieldVolume.Empty(DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize);
DistanceFieldVolume.AddZeroed(DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize);
for (int32 ZIndex = 0; ZIndex < DistanceField::BrickSize; ZIndex++)
{
for (int32 YIndex = 0; YIndex < DistanceField::BrickSize; YIndex++)
{
for (int32 XIndex = 0; XIndex < DistanceField::BrickSize; XIndex++)
{
if (XIndex == DebugX && YIndex == DebugY && ZIndex == DebugZ)
{
int32 DebugBreak = 0;
}
const FVector VoxelPosition = FVector(XIndex, YIndex, ZIndex) * DistanceFieldVoxelSize + BrickMinPosition;
const int32 Index = (ZIndex * DistanceField::BrickSize * DistanceField::BrickSize + YIndex * DistanceField::BrickSize + XIndex);
float MinLocalSpaceDistance = LocalSpaceTraceDistance;
bool bTraceRays = true;
if (bUsePointQuery)
{
RTCPointQuery PointQuery;
PointQuery.x = VoxelPosition.X;
PointQuery.y = VoxelPosition.Y;
PointQuery.z = VoxelPosition.Z;
PointQuery.time = 0;
PointQuery.radius = LocalSpaceTraceDistance;
FEmbreePointQueryContext QueryContext;
rtcInitPointQueryContext(&QueryContext);
QueryContext.MeshGeometry = EmbreeScene.Geometry.InternalGeometry;
QueryContext.NumTriangles = EmbreeScene.Geometry.TriangleDescs.Num();
float ClosestUnsignedDistanceSq = (LocalSpaceTraceDistance * 2.0f) * (LocalSpaceTraceDistance * 2.0f);
rtcPointQuery(EmbreeScene.EmbreeScene, &PointQuery, &QueryContext, EmbreePointQueryFunction, &ClosestUnsignedDistanceSq);
const float ClosestDistance = FMath::Sqrt(ClosestUnsignedDistanceSq);
bTraceRays = ClosestDistance <= LocalSpaceTraceDistance;
MinLocalSpaceDistance = FMath::Min(MinLocalSpaceDistance, ClosestDistance);
}
if (bTraceRays)
{
int32 Hit = 0;
int32 HitBack = 0;
for (int32 SampleIndex = 0; SampleIndex < SampleDirections->Num(); SampleIndex++)
{
const FVector UnitRayDirection = (*SampleDirections)[SampleIndex];
const float PullbackEpsilon = 1.e-4f;
// Pull back the starting position slightly to make sure we hit a triangle that VoxelPosition is exactly on.
// This happens a lot with boxes, since we trace from voxel corners.
const FVector StartPosition = VoxelPosition - PullbackEpsilon * LocalSpaceTraceDistance * UnitRayDirection;
const FVector EndPosition = VoxelPosition + UnitRayDirection * LocalSpaceTraceDistance;
if (FMath::LineBoxIntersection(VolumeBounds, VoxelPosition, EndPosition, UnitRayDirection))
{
FEmbreeRay EmbreeRay;
FVector RayDirection = EndPosition - VoxelPosition;
EmbreeRay.ray.org_x = StartPosition.X;
EmbreeRay.ray.org_y = StartPosition.Y;
EmbreeRay.ray.org_z = StartPosition.Z;
EmbreeRay.ray.dir_x = RayDirection.X;
EmbreeRay.ray.dir_y = RayDirection.Y;
EmbreeRay.ray.dir_z = RayDirection.Z;
EmbreeRay.ray.tnear = 0;
EmbreeRay.ray.tfar = 1.0f;
FEmbreeIntersectionContext EmbreeContext;
rtcInitIntersectContext(&EmbreeContext);
rtcIntersect1(EmbreeScene.EmbreeScene, &EmbreeContext, &EmbreeRay);
if (EmbreeRay.hit.geomID != RTC_INVALID_GEOMETRY_ID && EmbreeRay.hit.primID != RTC_INVALID_GEOMETRY_ID)
{
check(EmbreeContext.ElementIndex != -1);
Hit++;
const FVector HitNormal = EmbreeRay.GetHitNormal();
if (FVector::DotProduct(UnitRayDirection, HitNormal) > 0 && !EmbreeContext.IsHitTwoSided())
{
HitBack++;
}
if (!bUsePointQuery)
{
const float CurrentDistance = EmbreeRay.ray.tfar * LocalSpaceTraceDistance;
if (CurrentDistance < MinLocalSpaceDistance)
{
MinLocalSpaceDistance = CurrentDistance;
}
}
}
}
}
// Consider this voxel 'inside' an object if we hit a significant number of backfaces
if (Hit > 0 && HitBack > .25f * SampleDirections->Num())
{
MinLocalSpaceDistance *= -1;
}
}
// Transform to the tracing shader's Volume space
const float VolumeSpaceDistance = MinLocalSpaceDistance * LocalToVolumeScale;
// Transform to the Distance Field texture's space
const float RescaledDistance = (VolumeSpaceDistance - DistanceFieldToVolumeScaleBias.Y) / DistanceFieldToVolumeScaleBias.X;
check(DistanceField::DistanceFieldFormat == PF_G8);
const uint8 QuantizedDistance = FMath::Clamp<int32>(FMath::FloorToInt(RescaledDistance * 255.0f + .5f), 0, 255);
DistanceFieldVolume[Index] = QuantizedDistance;
BrickMaxDistance = FMath::Max(BrickMaxDistance, QuantizedDistance);
BrickMinDistance = FMath::Min(BrickMinDistance, QuantizedDistance);
}
}
}
}
void FMeshUtilities2::GenerateSignedDistanceFieldVolumeData(
FString MeshName,
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
const FStaticMeshLODResources& LODModel,
class FQueuedThreadPool& ThreadPool,
const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
const FBoxSphereBounds& Bounds,
float DistanceFieldResolutionScale,
bool bGenerateAsIfTwoSided,
FDistanceFieldVolumeData& OutData)
{
if (DistanceFieldResolutionScale > 0)
{
const double StartTime = FPlatformTime::Seconds();
FEmbreeScene EmbreeScene;
MeshRepresentation::SetupEmbreeScene(MeshName,
SourceMeshData,
LODModel,
MaterialBlendModes,
bGenerateAsIfTwoSided,
EmbreeScene);
check(EmbreeScene.bUseEmbree);
bool bMostlyTwoSided;
{
uint32 NumTrianglesTotal = 0;
uint32 NumTwoSidedTriangles = 0;
for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
{
const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
{
NumTrianglesTotal += Section.NumTriangles;
if (MaterialBlendModes[Section.MaterialIndex].bTwoSided)
{
NumTwoSidedTriangles += Section.NumTriangles;
}
}
}
bMostlyTwoSided = NumTwoSidedTriangles * 4 >= NumTrianglesTotal || bGenerateAsIfTwoSided;
}
// Whether to use an Embree Point Query to compute the closest unsigned distance. Rays will only be traced to determine backfaces visible for sign.
const bool bUsePointQuery = true;
TArray<FVector4> SampleDirections;
{
const int32 NumVoxelDistanceSamples = bUsePointQuery ? 120 : 1200;
FRandomStream RandomStream(0);
MeshUtilities::GenerateStratifiedUniformHemisphereSamples(NumVoxelDistanceSamples, RandomStream, SampleDirections);
TArray<FVector4> OtherHemisphereSamples;
MeshUtilities::GenerateStratifiedUniformHemisphereSamples(NumVoxelDistanceSamples, RandomStream, OtherHemisphereSamples);
for (int32 i = 0; i < OtherHemisphereSamples.Num(); i++)
{
FVector4 Sample = OtherHemisphereSamples[i];
Sample.Z *= -1;
SampleDirections.Add(Sample);
}
}
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DistanceFields.MaxPerMeshResolution"));
const int32 PerMeshMax = CVar->GetValueOnAnyThread();
// Meshes with explicit artist-specified scale can go higher
const int32 MaxNumBlocksOneDim = FMath::Min<int32>(FMath::DivideAndRoundNearest(DistanceFieldResolutionScale <= 1 ? PerMeshMax / 2 : PerMeshMax, DistanceField::UniqueDataBrickSize), DistanceField::MaxIndirectionDimension - 1);
static const auto CVarDensity = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.DistanceFields.DefaultVoxelDensity"));
const float VoxelDensity = CVarDensity->GetValueOnAnyThread();
const float NumVoxelsPerLocalSpaceUnit = VoxelDensity * DistanceFieldResolutionScale;
FBox LocalSpaceMeshBounds(Bounds.GetBox());
// Make sure the mesh bounding box has positive extents to handle planes
{
FVector MeshBoundsCenter = LocalSpaceMeshBounds.GetCenter();
FVector MeshBoundsExtent = FVector::Max(LocalSpaceMeshBounds.GetExtent(), FVector(1.0f, 1.0f, 1.0f));
LocalSpaceMeshBounds.Min = MeshBoundsCenter - MeshBoundsExtent;
LocalSpaceMeshBounds.Max = MeshBoundsCenter + MeshBoundsExtent;
}
// We sample on voxel corners and use central differencing for gradients, so a box mesh using two-sided materials whose vertices lie on LocalSpaceMeshBounds produces a zero gradient on intersection
// Expand the mesh bounds by a fraction of a voxel to allow room for a pullback on the hit location for computing the gradient.
// Only expand for two sided meshes as this adds significant Mesh SDF tracing cost
if (bMostlyTwoSided)
{
const FVector DesiredDimensions = FVector(LocalSpaceMeshBounds.GetSize() * FVector(NumVoxelsPerLocalSpaceUnit / (float)DistanceField::UniqueDataBrickSize));
const FIntVector Mip0IndirectionDimensions = FIntVector(
FMath::Clamp(FMath::RoundToInt(DesiredDimensions.X), 1, MaxNumBlocksOneDim),
FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Y), 1, MaxNumBlocksOneDim),
FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Z), 1, MaxNumBlocksOneDim));
const float CentralDifferencingExpandInVoxels = .25f;
const FVector TexelObjectSpaceSize = LocalSpaceMeshBounds.GetSize() / FVector(Mip0IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * CentralDifferencingExpandInVoxels));
LocalSpaceMeshBounds = LocalSpaceMeshBounds.ExpandBy(TexelObjectSpaceSize);
}
// The tracing shader uses a Volume space that is normalized by the maximum extent, to keep Volume space within [-1, 1], we must match that behavior when encoding
const float LocalToVolumeScale = 1.0f / LocalSpaceMeshBounds.GetExtent().GetMax();
const FVector DesiredDimensions = FVector(LocalSpaceMeshBounds.GetSize() * FVector(NumVoxelsPerLocalSpaceUnit / (float)DistanceField::UniqueDataBrickSize));
const FIntVector Mip0IndirectionDimensions = FIntVector(
FMath::Clamp(FMath::RoundToInt(DesiredDimensions.X), 1, MaxNumBlocksOneDim),
FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Y), 1, MaxNumBlocksOneDim),
FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Z), 1, MaxNumBlocksOneDim));
TArray<uint8> StreamableMipData;
for (int32 MipIndex = 0; MipIndex < DistanceField::NumMips; MipIndex++)
{
const FIntVector IndirectionDimensions = FIntVector(
FMath::DivideAndRoundUp(Mip0IndirectionDimensions.X, 1 << MipIndex),
FMath::DivideAndRoundUp(Mip0IndirectionDimensions.Y, 1 << MipIndex),
FMath::DivideAndRoundUp(Mip0IndirectionDimensions.Z, 1 << MipIndex));
// Expand to guarantee one voxel border for gradient reconstruction using bilinear filtering
const FVector TexelObjectSpaceSize = LocalSpaceMeshBounds.GetSize() / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * DistanceField::MeshDistanceFieldObjectBorder));
const FBox DistanceFieldVolumeBounds = LocalSpaceMeshBounds.ExpandBy(TexelObjectSpaceSize);
const FVector IndirectionVoxelSize = DistanceFieldVolumeBounds.GetSize() / FVector(IndirectionDimensions);
const float IndirectionVoxelRadius = IndirectionVoxelSize.Size();
const FVector VolumeSpaceDistanceFieldVoxelSize = IndirectionVoxelSize * LocalToVolumeScale / FVector(DistanceField::UniqueDataBrickSize);
const float MaxDistanceForEncoding = VolumeSpaceDistanceFieldVoxelSize.Size() * DistanceField::BandSizeInVoxels;
const float LocalSpaceTraceDistance = MaxDistanceForEncoding / LocalToVolumeScale;
const FVector2D DistanceFieldToVolumeScaleBias(2.0f * MaxDistanceForEncoding, -MaxDistanceForEncoding);
TArray<FSparseMeshDistanceFieldAsyncTask> AsyncTasks;
AsyncTasks.Reserve(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z / 8);
for (int32 ZIndex = 0; ZIndex < IndirectionDimensions.Z; ZIndex++)
{
for (int32 YIndex = 0; YIndex < IndirectionDimensions.Y; YIndex++)
{
for (int32 XIndex = 0; XIndex < IndirectionDimensions.X; XIndex++)
{
AsyncTasks.Emplace(
EmbreeScene,
&SampleDirections,
LocalSpaceTraceDistance,
DistanceFieldVolumeBounds,
LocalToVolumeScale,
DistanceFieldToVolumeScaleBias,
FIntVector(XIndex, YIndex, ZIndex),
IndirectionDimensions,
bUsePointQuery);
}
}
}
static bool bMultiThreaded = true;
if (bMultiThreaded)
{
EParallelForFlags Flags = EParallelForFlags::BackgroundPriority | EParallelForFlags::Unbalanced;
ParallelForTemplate(AsyncTasks.Num(), [&AsyncTasks](int32 TaskIndex)
{
AsyncTasks[TaskIndex].DoWork();
}, Flags);
}
else
{
for (FSparseMeshDistanceFieldAsyncTask& AsyncTask : AsyncTasks)
{
AsyncTask.DoWork();
}
}
FSparseDistanceFieldMip& OutMip = OutData.Mips[MipIndex];
TArray<uint32> IndirectionTable;
IndirectionTable.Empty(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z);
IndirectionTable.AddUninitialized(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z);
for (int32 i = 0; i < IndirectionTable.Num(); i++)
{
IndirectionTable[i] = DistanceField::InvalidBrickIndex;
}
TArray<FSparseMeshDistanceFieldAsyncTask*> ValidBricks;
ValidBricks.Empty(AsyncTasks.Num());
for (int32 TaskIndex = 0; TaskIndex < AsyncTasks.Num(); TaskIndex++)
{
if (AsyncTasks[TaskIndex].BrickMinDistance < MAX_uint8 && AsyncTasks[TaskIndex].BrickMaxDistance > MIN_uint8)
{
ValidBricks.Add(&AsyncTasks[TaskIndex]);
}
}
const uint32 NumBricks = ValidBricks.Num();
const uint32 BrickSizeBytes = DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize * GPixelFormats[DistanceField::DistanceFieldFormat].BlockBytes;
TArray<uint8> DistanceFieldBrickData;
DistanceFieldBrickData.Empty(BrickSizeBytes * NumBricks);
DistanceFieldBrickData.AddUninitialized(BrickSizeBytes * NumBricks);
for (int32 BrickIndex = 0; BrickIndex < ValidBricks.Num(); BrickIndex++)
{
const FSparseMeshDistanceFieldAsyncTask& Brick = *ValidBricks[BrickIndex];
const int32 IndirectionIndex = ComputeLinearVoxelIndex(Brick.BrickCoordinate, IndirectionDimensions);
IndirectionTable[IndirectionIndex] = BrickIndex;
check(BrickSizeBytes == Brick.DistanceFieldVolume.Num() * Brick.DistanceFieldVolume.GetTypeSize());
FPlatformMemory::Memcpy(&DistanceFieldBrickData[BrickIndex * BrickSizeBytes], Brick.DistanceFieldVolume.GetData(), Brick.DistanceFieldVolume.Num() * Brick.DistanceFieldVolume.GetTypeSize());
}
const int32 IndirectionTableBytes = IndirectionTable.Num() * IndirectionTable.GetTypeSize();
const int32 MipDataBytes = IndirectionTableBytes + DistanceFieldBrickData.Num();
if (MipIndex == DistanceField::NumMips - 1)
{
OutData.AlwaysLoadedMip.Empty(MipDataBytes);
OutData.AlwaysLoadedMip.AddUninitialized(MipDataBytes);
FPlatformMemory::Memcpy(&OutData.AlwaysLoadedMip[0], IndirectionTable.GetData(), IndirectionTableBytes);
if (DistanceFieldBrickData.Num() > 0)
{
FPlatformMemory::Memcpy(&OutData.AlwaysLoadedMip[IndirectionTableBytes], DistanceFieldBrickData.GetData(), DistanceFieldBrickData.Num());
}
}
else
{
OutMip.BulkOffset = StreamableMipData.Num();
StreamableMipData.AddUninitialized(MipDataBytes);
OutMip.BulkSize = StreamableMipData.Num() - OutMip.BulkOffset;
checkf(OutMip.BulkSize > 0, TEXT("BulkSize was 0 for %s with %ux%ux%u indirection"), *MeshName, IndirectionDimensions.X, IndirectionDimensions.Y, IndirectionDimensions.Z);
FPlatformMemory::Memcpy(&StreamableMipData[OutMip.BulkOffset], IndirectionTable.GetData(), IndirectionTableBytes);
if (DistanceFieldBrickData.Num() > 0)
{
FPlatformMemory::Memcpy(&StreamableMipData[OutMip.BulkOffset + IndirectionTableBytes], DistanceFieldBrickData.GetData(), DistanceFieldBrickData.Num());
}
}
OutMip.IndirectionDimensions = IndirectionDimensions;
OutMip.DistanceFieldToVolumeScaleBias = DistanceFieldToVolumeScaleBias;
OutMip.NumDistanceFieldBricks = NumBricks;
// Account for the border voxels we added
const FVector VirtualUVMin = FVector(DistanceField::MeshDistanceFieldObjectBorder) / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize);
const FVector VirtualUVSize = FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * DistanceField::MeshDistanceFieldObjectBorder)) / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize);
const FVector VolumePositionExtent = LocalSpaceMeshBounds.GetExtent() * LocalToVolumeScale;
// [-VolumePositionExtent, VolumePositionExtent] -> [VirtualUVMin, VirtualUVMin + VirtualUVSize]
OutMip.VolumeToVirtualUVScale = VirtualUVSize / (2 * VolumePositionExtent);
OutMip.VolumeToVirtualUVAdd = VolumePositionExtent * OutMip.VolumeToVirtualUVScale + VirtualUVMin;
}
MeshRepresentation::DeleteEmbreeScene(EmbreeScene);
OutData.bMostlyTwoSided = bMostlyTwoSided;
OutData.LocalSpaceMeshBounds = LocalSpaceMeshBounds;
OutData.StreamableMips.Lock(LOCK_READ_WRITE);
uint8* Ptr = (uint8*)OutData.StreamableMips.Realloc(StreamableMipData.Num());
FMemory::Memcpy(Ptr, StreamableMipData.GetData(), StreamableMipData.Num());
OutData.StreamableMips.Unlock();
OutData.StreamableMips.SetBulkDataFlags(BULKDATA_Force_NOT_InlinePayload);
const float BuildTime = (float)(FPlatformTime::Seconds() - StartTime);
if (BuildTime > 1.0f)
{
UE_LOG(LogMeshUtilities, Log, TEXT("完成:距离场构建 %.1fs - %ux%ux%u 稀疏距离场, %.1fMb total, %.1fMb 总是加载, %u%% occupied, %u 三角形, %s"),
BuildTime,
Mip0IndirectionDimensions.X * DistanceField::UniqueDataBrickSize,
Mip0IndirectionDimensions.Y * DistanceField::UniqueDataBrickSize,
Mip0IndirectionDimensions.Z * DistanceField::UniqueDataBrickSize,
(OutData.GetResourceSizeBytes() + OutData.StreamableMips.GetBulkDataSize()) / 1024.0f / 1024.0f,
(OutData.AlwaysLoadedMip.GetAllocatedSize()) / 1024.0f / 1024.0f,
FMath::RoundToInt(100.0f * OutData.Mips[0].NumDistanceFieldBricks / (float)(Mip0IndirectionDimensions.X * Mip0IndirectionDimensions.Y * Mip0IndirectionDimensions.Z)),
EmbreeScene.NumIndices / 3,
*MeshName);
}
}
}
#else
void FMeshUtilities2::GenerateSignedDistanceFieldVolumeData(
FString MeshName,
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
const FStaticMeshLODResources& LODModel,
class FQueuedThreadPool& ThreadPool,
const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
const FBoxSphereBounds& Bounds,
float DistanceFieldResolutionScale,
bool bGenerateAsIfTwoSided,
FDistanceFieldVolumeData& OutData)
{
if (DistanceFieldResolutionScale > 0)
{
UE_LOG(LogMeshUtilities, Warning, TEXT("Couldn't generate distance field for mesh, platform is missing Embree support."));
}
}
#endif // PLATFORM_ENABLE_VECTORINTRINSICS

View File

@@ -0,0 +1,318 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "MeshRepresentationCommon.h"
#include "MeshUtilities2/Public/MeshUtilities2.h"
#include "MeshUtilitiesPrivate.h"
#include "DerivedMeshDataTaskUtils.h"
void MeshUtilities::GenerateStratifiedUniformHemisphereSamples(int32 NumSamples, FRandomStream& RandomStream, TArray<FVector4>& Samples)
{
const int32 NumThetaSteps = FMath::TruncToInt(FMath::Sqrt(NumSamples / (2.0f * (float)PI)));
const int32 NumPhiSteps = FMath::TruncToInt(NumThetaSteps * (float)PI);
Samples.Empty(NumThetaSteps * NumPhiSteps);
for (int32 ThetaIndex = 0; ThetaIndex < NumThetaSteps; ThetaIndex++)
{
for (int32 PhiIndex = 0; PhiIndex < NumPhiSteps; PhiIndex++)
{
const float U1 = RandomStream.GetFraction();
const float U2 = RandomStream.GetFraction();
const float Fraction1 = (ThetaIndex + U1) / (float)NumThetaSteps;
const float Fraction2 = (PhiIndex + U2) / (float)NumPhiSteps;
const float R = FMath::Sqrt(1.0f - Fraction1 * Fraction1);
const float Phi = 2.0f * (float)PI * Fraction2;
// Convert to Cartesian
Samples.Add(FVector4(FMath::Cos(Phi) * R, FMath::Sin(Phi) * R, Fraction1));
}
}
}
// [Frisvad 2012, "Building an Orthonormal Basis from a 3D Unit Vector Without Normalization"]
FMatrix MeshRepresentation::GetTangentBasisFrisvad(FVector TangentZ)
{
FVector TangentX;
FVector TangentY;
if (TangentZ.Z < -0.9999999f)
{
TangentX = FVector(0, -1, 0);
TangentY = FVector(-1, 0, 0);
}
else
{
float A = 1.0f / (1.0f + TangentZ.Z);
float B = -TangentZ.X * TangentZ.Y * A;
TangentX = FVector(1.0f - TangentZ.X * TangentZ.X * A, B, -TangentZ.X);
TangentY = FVector(B, 1.0f - TangentZ.Y * TangentZ.Y * A, -TangentZ.Y);
}
FMatrix LocalBasis;
LocalBasis.SetIdentity();
LocalBasis.SetAxis(0, TangentX);
LocalBasis.SetAxis(1, TangentY);
LocalBasis.SetAxis(2, TangentZ);
return LocalBasis;
}
#if USE_EMBREE
void EmbreeFilterFunc(const struct RTCFilterFunctionNArguments* args)
{
FEmbreeGeometry* EmbreeGeometry = (FEmbreeGeometry*)args->geometryUserPtr;
FEmbreeTriangleDesc Desc = EmbreeGeometry->TriangleDescs[RTCHitN_primID(args->hit, 1, 0)];
FEmbreeIntersectionContext& IntersectionContext = *static_cast<FEmbreeIntersectionContext*>(args->context);
IntersectionContext.ElementIndex = Desc.ElementIndex;
}
void EmbreeErrorFunc(void* userPtr, RTCError code, const char* str)
{
FString ErrorString;
TArray<TCHAR>& ErrorStringArray = ErrorString.GetCharArray();
ErrorStringArray.Empty();
int32 StrLen = FCStringAnsi::Strlen(str);
int32 Length = FUTF8ToTCHAR_Convert::ConvertedLength(str, StrLen);
ErrorStringArray.AddUninitialized(Length + 1); // +1 for the null terminator
FUTF8ToTCHAR_Convert::Convert(ErrorStringArray.GetData(), ErrorStringArray.Num(), reinterpret_cast<const ANSICHAR*>(str), StrLen);
ErrorStringArray[Length] = TEXT('\0');
UE_LOG(LogMeshUtilities, Error, TEXT("Embree error: %s Code=%u"), *ErrorString, (uint32)code);
}
#endif
void MeshRepresentation::SetupEmbreeScene(
FString MeshName,
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
const FStaticMeshLODResources& LODModel,
const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
bool bGenerateAsIfTwoSided,
FEmbreeScene& EmbreeScene)
{
const uint32 NumIndices = SourceMeshData.IsValid() ? SourceMeshData.GetNumIndices() : LODModel.IndexBuffer.GetNumIndices();
const int32 NumTriangles = NumIndices / 3;
const uint32 NumVertices = SourceMeshData.IsValid() ? SourceMeshData.GetNumVertices() : LODModel.VertexBuffers.PositionVertexBuffer.GetNumVertices();
EmbreeScene.NumIndices = NumTriangles;
TArray<FkDOPBuildCollisionTriangle<uint32> > BuildTriangles;
#if USE_EMBREE
EmbreeScene.bUseEmbree = true;
if (EmbreeScene.bUseEmbree)
{
EmbreeScene.EmbreeDevice = rtcNewDevice(nullptr);
rtcSetDeviceErrorFunction(EmbreeScene.EmbreeDevice, EmbreeErrorFunc, nullptr);
RTCError ReturnErrorNewDevice = rtcGetDeviceError(EmbreeScene.EmbreeDevice);
if (ReturnErrorNewDevice != RTC_ERROR_NONE)
{
UE_LOG(LogMeshUtilities, Warning, TEXT("GenerateSignedDistanceFieldVolumeData failed for %s. Embree rtcNewDevice failed. Code: %d"), *MeshName, (int32)ReturnErrorNewDevice);
return;
}
EmbreeScene.EmbreeScene = rtcNewScene(EmbreeScene.EmbreeDevice);
rtcSetSceneFlags(EmbreeScene.EmbreeScene, RTC_SCENE_FLAG_NONE);
RTCError ReturnErrorNewScene = rtcGetDeviceError(EmbreeScene.EmbreeDevice);
if (ReturnErrorNewScene != RTC_ERROR_NONE)
{
UE_LOG(LogMeshUtilities, Warning, TEXT("GenerateSignedDistanceFieldVolumeData failed for %s. Embree rtcNewScene failed. Code: %d"), *MeshName, (int32)ReturnErrorNewScene);
rtcReleaseDevice(EmbreeScene.EmbreeDevice);
return;
}
}
#endif
TArray<int32> FilteredTriangles;
FilteredTriangles.Empty(NumTriangles);
if (SourceMeshData.IsValid())
{
for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
{
const uint32 I0 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 0];
const uint32 I1 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 1];
const uint32 I2 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 2];
const FVector V0 = SourceMeshData.VertexPositions[I0];
const FVector V1 = SourceMeshData.VertexPositions[I1];
const FVector V2 = SourceMeshData.VertexPositions[I2];
const FVector TriangleNormal = ((V1 - V2) ^ (V0 - V2));
const bool bDegenerateTriangle = TriangleNormal.SizeSquared() < SMALL_NUMBER;
if (!bDegenerateTriangle)
{
FilteredTriangles.Add(TriangleIndex);
}
}
}
else
{
for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
{
const FIndexArrayView Indices = LODModel.IndexBuffer.GetArrayView();
const uint32 I0 = Indices[TriangleIndex * 3 + 0];
const uint32 I1 = Indices[TriangleIndex * 3 + 1];
const uint32 I2 = Indices[TriangleIndex * 3 + 2];
const FVector V0 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I0);
const FVector V1 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I1);
const FVector V2 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I2);
const FVector TriangleNormal = ((V1 - V2) ^ (V0 - V2));
const bool bDegenerateTriangle = TriangleNormal.SizeSquared() < SMALL_NUMBER;
if (!bDegenerateTriangle)
{
bool bTriangleIsOpaqueOrMasked = false;
for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
{
const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
if ((uint32)(TriangleIndex * 3) >= Section.FirstIndex && (uint32)(TriangleIndex * 3) < Section.FirstIndex + Section.NumTriangles * 3)
{
if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
{
bTriangleIsOpaqueOrMasked = !IsTranslucentBlendMode(MaterialBlendModes[Section.MaterialIndex].BlendMode);
}
break;
}
}
if (bTriangleIsOpaqueOrMasked)
{
FilteredTriangles.Add(TriangleIndex);
}
}
}
}
EmbreeScene.Geometry.VertexArray.Empty(NumVertices);
EmbreeScene.Geometry.VertexArray.AddUninitialized(NumVertices);
const int32 NumFilteredIndices = FilteredTriangles.Num() * 3;
EmbreeScene.Geometry.IndexArray.Empty(NumFilteredIndices);
EmbreeScene.Geometry.IndexArray.AddUninitialized(NumFilteredIndices);
FVector* EmbreeVertices = EmbreeScene.Geometry.VertexArray.GetData();
uint32* EmbreeIndices = EmbreeScene.Geometry.IndexArray.GetData();
EmbreeScene.Geometry.TriangleDescs.Empty(FilteredTriangles.Num());
for (int32 FilteredTriangleIndex = 0; FilteredTriangleIndex < FilteredTriangles.Num(); FilteredTriangleIndex++)
{
uint32 I0, I1, I2;
FVector V0, V1, V2;
const int32 TriangleIndex = FilteredTriangles[FilteredTriangleIndex];
if (SourceMeshData.IsValid())
{
I0 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 0];
I1 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 1];
I2 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 2];
V0 = SourceMeshData.VertexPositions[I0];
V1 = SourceMeshData.VertexPositions[I1];
V2 = SourceMeshData.VertexPositions[I2];
}
else
{
const FIndexArrayView Indices = LODModel.IndexBuffer.GetArrayView();
I0 = Indices[TriangleIndex * 3 + 0];
I1 = Indices[TriangleIndex * 3 + 1];
I2 = Indices[TriangleIndex * 3 + 2];
V0 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I0);
V1 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I1);
V2 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I2);
}
bool bTriangleIsTwoSided = false;
for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
{
const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
if ((uint32)(TriangleIndex * 3) >= Section.FirstIndex && (uint32)(TriangleIndex * 3) < Section.FirstIndex + Section.NumTriangles * 3)
{
if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
{
bTriangleIsTwoSided = MaterialBlendModes[Section.MaterialIndex].bTwoSided;
}
break;
}
}
if (EmbreeScene.bUseEmbree)
{
EmbreeIndices[FilteredTriangleIndex * 3 + 0] = I0;
EmbreeIndices[FilteredTriangleIndex * 3 + 1] = I1;
EmbreeIndices[FilteredTriangleIndex * 3 + 2] = I2;
EmbreeVertices[I0] = V0;
EmbreeVertices[I1] = V1;
EmbreeVertices[I2] = V2;
FEmbreeTriangleDesc Desc;
// Store bGenerateAsIfTwoSided in material index
Desc.ElementIndex = bGenerateAsIfTwoSided || bTriangleIsTwoSided ? 1 : 0;
EmbreeScene.Geometry.TriangleDescs.Add(Desc);
}
else
{
BuildTriangles.Add(FkDOPBuildCollisionTriangle<uint32>(
// Store bGenerateAsIfTwoSided in material index
bGenerateAsIfTwoSided || bTriangleIsTwoSided ? 1 : 0,
V0,
V1,
V2));
}
}
#if USE_EMBREE
if (EmbreeScene.bUseEmbree)
{
RTCGeometry Geometry = rtcNewGeometry(EmbreeScene.EmbreeDevice, RTC_GEOMETRY_TYPE_TRIANGLE);
EmbreeScene.Geometry.InternalGeometry = Geometry;
rtcSetSharedGeometryBuffer(Geometry, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, EmbreeVertices, 0, sizeof(FVector), NumVertices);
rtcSetSharedGeometryBuffer(Geometry, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, EmbreeIndices, 0, sizeof(uint32) * 3, FilteredTriangles.Num());
rtcSetGeometryUserData(Geometry, &EmbreeScene.Geometry);
rtcSetGeometryIntersectFilterFunction(Geometry, EmbreeFilterFunc);
rtcCommitGeometry(Geometry);
rtcAttachGeometry(EmbreeScene.EmbreeScene, Geometry);
rtcReleaseGeometry(Geometry);
rtcCommitScene(EmbreeScene.EmbreeScene);
RTCError ReturnError = rtcGetDeviceError(EmbreeScene.EmbreeDevice);
if (ReturnError != RTC_ERROR_NONE)
{
UE_LOG(LogMeshUtilities, Warning, TEXT("GenerateSignedDistanceFieldVolumeData failed for %s. Embree rtcCommitScene failed. Code: %d"), *MeshName, (int32)ReturnError);
return;
}
}
else
#endif
{
EmbreeScene.kDopTree.Build(BuildTriangles);
}
}
void MeshRepresentation::DeleteEmbreeScene(FEmbreeScene& EmbreeScene)
{
#if USE_EMBREE
if (EmbreeScene.bUseEmbree)
{
rtcReleaseScene(EmbreeScene.EmbreeScene);
rtcReleaseDevice(EmbreeScene.EmbreeDevice);
}
#endif
}

View File

@@ -0,0 +1,158 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "MeshUtilities2/Public/MeshUtilities2.h"
#include "kDOP.h"
#include "MeshUtilities2/Public/DistanceFieldAtlas2.h"
#if USE_EMBREE
#include <embree3/rtcore.h>
#include <embree3/rtcore_ray.h>
#else
typedef void* RTCDevice;
typedef void* RTCScene;
typedef void* RTCGeometry;
#endif
class FSourceMeshDataForDerivedDataTask;
class FMeshBuildDataProvider
{
public:
/** Initialization constructor. */
FMeshBuildDataProvider(
const TkDOPTree<const FMeshBuildDataProvider, uint32>& InkDopTree) :
kDopTree(InkDopTree)
{}
// kDOP data provider interface.
FORCEINLINE const TkDOPTree<const FMeshBuildDataProvider, uint32>& GetkDOPTree(void) const
{
return kDopTree;
}
FORCEINLINE const FMatrix& GetLocalToWorld(void) const
{
return FMatrix::Identity;
}
FORCEINLINE const FMatrix& GetWorldToLocal(void) const
{
return FMatrix::Identity;
}
FORCEINLINE FMatrix GetLocalToWorldTransposeAdjoint(void) const
{
return FMatrix::Identity;
}
FORCEINLINE float GetDeterminant(void) const
{
return 1.0f;
}
private:
const TkDOPTree<const FMeshBuildDataProvider, uint32>& kDopTree;
};
struct FEmbreeTriangleDesc
{
int16 ElementIndex;
};
// Mapping between Embree Geometry Id and engine Mesh/LOD Id
struct FEmbreeGeometry
{
TArray<uint32> IndexArray;
TArray<FVector> VertexArray;
TArray<FEmbreeTriangleDesc> TriangleDescs; // The material ID of each triangle.
RTCGeometry InternalGeometry;
};
class FEmbreeScene
{
public:
bool bUseEmbree = false;
int32 NumIndices = 0;
// Embree
RTCDevice EmbreeDevice = nullptr;
RTCScene EmbreeScene = nullptr;
FEmbreeGeometry Geometry;
// DOP tree fallback
TkDOPTree<const FMeshBuildDataProvider, uint32> kDopTree;
};
#if USE_EMBREE
struct FEmbreeRay : public RTCRayHit
{
FEmbreeRay() :
ElementIndex(-1)
{
hit.u = hit.v = 0;
ray.time = 0;
ray.mask = 0xFFFFFFFF;
hit.geomID = RTC_INVALID_GEOMETRY_ID;
hit.instID[0] = RTC_INVALID_GEOMETRY_ID;
hit.primID = RTC_INVALID_GEOMETRY_ID;
}
FVector GetHitNormal() const
{
return FVector(-hit.Ng_x, -hit.Ng_y, -hit.Ng_z).GetSafeNormal();
}
bool IsHitTwoSided() const
{
// MaterialIndex on the build triangles was set to 1 if two-sided, or 0 if one-sided
return ElementIndex == 1;
}
// Additional Outputs.
int32 ElementIndex; // Material Index
};
struct FEmbreeIntersectionContext : public RTCIntersectContext
{
FEmbreeIntersectionContext() :
ElementIndex(-1)
{}
bool IsHitTwoSided() const
{
// MaterialIndex on the build triangles was set to 1 if two-sided, or 0 if one-sided
return ElementIndex == 1;
}
// Additional Outputs.
int32 ElementIndex; // Material Index
};
#endif
namespace MeshRepresentation
{
/**
* Generates unit length, stratified and uniformly distributed direction samples in a hemisphere.
*/
void GenerateStratifiedUniformHemisphereSamples(int32 NumSamples, FRandomStream& RandomStream, TArray<FVector4>& Samples);
/**
* [Frisvad 2012, "Building an Orthonormal Basis from a 3D Unit Vector Without Normalization"]
*/
FMatrix GetTangentBasisFrisvad(FVector TangentZ);
void SetupEmbreeScene(FString MeshName,
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
const FStaticMeshLODResources& LODModel,
const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
bool bGenerateAsIfTwoSided,
FEmbreeScene& EmbreeScene);
void DeleteEmbreeScene(FEmbreeScene& EmbreeScene);
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,259 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
//#include "MeshUtilities.h"
// #include "IAnimationBlueprintEditor.h"
// #include "IAnimationBlueprintEditorModule.h"
// #include "IAnimationEditor.h"
// #include "IAnimationEditorModule.h"
// #include "ISkeletalMeshEditor.h"
// #include "ISkeletalMeshEditorModule.h"
// #include "ISkeletonEditor.h"
// #include "ISkeletonEditorModule.h"
#include "Engine/StaticMesh.h"
#include "MeshUtilities2/Public/DistanceFieldAtlas2.h"
#include "MeshUtilities2/Public/MeshUtilities2.h"
class FProcessAsyncTasksTickObject : FTickableGameObject
{
public:
virtual bool IsTickableInEditor() const override { return true; }
virtual void Tick(float DeltaTime) override;
virtual TStatId GetStatId() const { return TStatId(); }
};
class FMeshUtilities2 : public IMeshUtilities2
{
public:
// UE_DEPRECATED(4.17, "Use functionality in new MeshReduction Module")
// virtual IMeshReduction* GetStaticMeshReductionInterface() override;
//
// UE_DEPRECATED(4.17, "Use functionality in new MeshReduction Module")
// virtual IMeshReduction* GetSkeletalMeshReductionInterface() override;
//
// UE_DEPRECATED(4.17, "Use functionality in new MeshReduction Module")
// virtual IMeshMerging* GetMeshMergingInterface() override;
//UE_DEPRECATED(4.17, "Use functionality in new MeshMergeUtilities Module")
//virtual void MergeActors(
// const TArray<AActor*>& SourceActors,
// const FMeshMergingSettings& InSettings,
// UPackage* InOuter,
// const FString& InBasePackageName,
// TArray<UObject*>& OutAssetsToSync,
// FVector& OutMergedActorLocation,
// bool bSilent = false) const override;
//UE_DEPRECATED(4.17, "Use functionality in new MeshMergeUtilities Module")
//virtual void MergeStaticMeshComponents(
// const TArray<UStaticMeshComponent*>& ComponentsToMerge,
// UWorld* World,
// const FMeshMergingSettings& InSettings,
// UPackage* InOuter,
// const FString& InBasePackageName,
// TArray<UObject*>& OutAssetsToSync,
// FVector& OutMergedActorLocation,
// const float ScreenSize,
// bool bSilent = false) const override;
//UE_DEPRECATED(4.17, "Use functionality in new MeshMergeUtilities Module")
//virtual void CreateProxyMesh(const TArray<AActor*>& InActors, const struct FMeshProxySettings& InMeshProxySettings, UPackage* InOuter, const FString& InProxyBasePackageName, const FGuid InGuid, FCreateProxyDelegate InProxyCreatedDelegate, const bool bAllowAsync,
//const float ScreenAreaSize = 1.0f) override;
//UE_DEPRECATED(4.17, "Function is removed, use functionality in new MeshMergeUtilities Module")
//virtual void FlattenMaterialsWithMeshData(TArray<UMaterialInterface*>& InMaterials, TArray<FRawMeshExt>& InSourceMeshes, TMap<FMeshIdAndLOD, TArray<int32>>& InMaterialIndexMap, TArray<bool>& InMeshShouldBakeVertexData, const FMaterialProxySettings &InMaterialProxySettings, TArray<FFlattenMaterial> &OutFlattenedMaterials) const override;
private:
FProcessAsyncTasksTickObject* TickObject;
/** Cached version string. */
FString VersionString;
/** True if NvTriStrip is being used for tri order optimization. */
bool bUsingNvTriStrip;
/** True if we disable triangle order optimization. For debugging purposes only */
bool bDisableTriangleOrderOptimization;
// IMeshUtilities interface.
virtual const FString& GetVersionString() const override
{
return VersionString;
}
virtual void FixupMaterialSlotNames(UStaticMesh* StaticMesh) const override;
virtual void FixupMaterialSlotNames(USkeletalMesh* SkeletalMesh) const override;
//virtual bool BuildStaticMesh(
// FStaticMeshRenderData& OutRenderData,
// UStaticMesh* StaticMesh,
// const FStaticMeshLODGroup& LODGroup
// ) override;
virtual void BuildStaticMeshVertexAndIndexBuffers(
TArray<FStaticMeshBuildVertex>& OutVertices,
TArray<TArray<uint32>>& OutPerSectionIndices,
TArray<int32>& OutWedgeMap,
const FRawMesh& RawMesh,
const FOverlappingCorners& OverlappingCorners,
const TMap<uint32, uint32>& MaterialToSectionMapping,
float ComparisonThreshold,
FVector BuildScale,
int32 ImportVersion
) override;
//virtual bool GenerateStaticMeshLODs(UStaticMesh* StaticMesh, const FStaticMeshLODGroup& LODGroup) override;
virtual void GenerateSignedDistanceFieldVolumeData(
FString MeshName,
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
const FStaticMeshLODResources& LODModel,
class FQueuedThreadPool& ThreadPool,
const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
const FBoxSphereBounds& Bounds,
float DistanceFieldResolutionScale,
bool bGenerateAsIfTwoSided,
FDistanceFieldVolumeData& OutData) override;
virtual bool GenerateCardRepresentationData(
FString MeshName,
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
const FStaticMeshLODResources& LODModel,
class FQueuedThreadPool& ThreadPool,
const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
const FBoxSphereBounds& Bounds,
const FDistanceFieldVolumeData* DistanceFieldVolumeData,
bool bGenerateAsIfTwoSided,
class FCardRepresentationData& OutData) override;
virtual void RecomputeTangentsAndNormalsForRawMesh(bool bRecomputeTangents, bool bRecomputeNormals, const FMeshBuildSettings& InBuildSettings, FRawMesh& OutRawMesh) const override;
virtual void RecomputeTangentsAndNormalsForRawMesh(bool bRecomputeTangents, bool bRecomputeNormals, const FMeshBuildSettings& InBuildSettings, const FOverlappingCorners& InOverlappingCorners, FRawMesh& OutRawMesh) const override;
//virtual bool GenerateUniqueUVsForStaticMesh(const FRawMesh& RawMesh, int32 TextureResolution, TArray<FVector2D>& OutTexCoords) const override;
//virtual bool GenerateUniqueUVsForStaticMesh(const FRawMesh& RawMesh, int32 TextureResolution, bool bMergeIdenticalMaterials, TArray<FVector2D>& OutTexCoords) const override;
//virtual bool BuildSkeletalMesh(FSkeletalMeshLODModel& LODModel, const FString& SkeletalMeshName, 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 = MeshBuildOptions(), TArray<FText> * OutWarningMessages = NULL, TArray<FName> * OutWarningNames = NULL) override;
//UE_DEPRECATED(4.24, "Use functionality in FSkeletalMeshUtilityBuilder instead.")
//bool BuildSkeletalMesh_Legacy(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 FOverlappingThresholds& OverlappingThresholds, bool bComputeNormals = true, bool bComputeTangents = true, bool bComputeWeightedNormals = true, TArray<FText> * OutWarningMessages = NULL, TArray<FName> * OutWarningNames = NULL);
virtual void CacheOptimizeIndexBuffer(TArray<uint16>& Indices) override;
virtual void CacheOptimizeIndexBuffer(TArray<uint32>& Indices) override;
void CacheOptimizeVertexAndIndexBuffer(TArray<FStaticMeshBuildVertex>& Vertices, TArray<TArray<uint32>>& PerSectionIndices, TArray<int32>& WedgeMap);
virtual void BuildSkeletalAdjacencyIndexBuffer(
const TArray<FSoftSkinVertex>& VertexBuffer,
const uint32 TexCoordCount,
const TArray<uint32>& Indices,
TArray<uint32>& OutPnAenIndices
) override;
/**
* Calculate The tangent bi normal and normal for the triangle define by the tree SoftSkinVertex.
*
* @note The function will always fill properly the OutTangents array with 3 FVector. If the triangle is degenerated the OutTangent will contain zeroed vectors.
*
* @param VertexA - First triangle vertex.
* @param VertexB - Second triangle vertex.
* @param VertexC - Third triangle vertex.
* @param OutTangents - The function allocate the TArray with 3 FVector, to represent the triangle tangent, bi normal and normal.
* @param CompareThreshold - The threshold use to compare a tangent vector with zero.
*/
virtual void CalculateTriangleTangent(const FSoftSkinVertex& VertexA, const FSoftSkinVertex& VertexB, const FSoftSkinVertex& VertexC, TArray<FVector>& OutTangents, float CompareThreshold) override;
virtual void CalcBoneVertInfos(USkeletalMesh* SkeletalMesh, TArray<FBoneVertInfo2>& Infos, bool bOnlyDominant) override;
/**
* Convert a set of mesh components in their current pose to a static mesh.
* @param InMeshComponents The mesh components we want to convert
* @param InRootTransform The transform of the root of the mesh we want to output
* @param InPackageName The package name to create the static mesh in. If this is empty then a dialog will be displayed to pick the mesh.
* @return a new static mesh (specified by the user)
*/
virtual UStaticMesh* ConvertMeshesToStaticMesh(const TArray<UMeshComponent*>& InMeshComponents, const FTransform& InRootTransform = FTransform::Identity, const FString& InPackageName = FString()) override;
/**
* Builds a renderable skeletal mesh LOD model. Note that the array of chunks
* will be destroyed during this process!
* @param LODModel Upon return contains a renderable skeletal mesh LOD model.
* @param RefSkeleton The reference skeleton associated with the model.
* @param Chunks Skinned mesh chunks from which to build the renderable model.
* @param PointToOriginalMap Maps a vertex's RawPointIdx to its index at import time.
*/
//void BuildSkeletalModelFromChunks(FSkeletalMeshLODModel& LODModel, const FReferenceSkeleton& RefSkeleton, TArray<FSkinnedMeshChunk*>& Chunks, const TArray<int32>& PointToOriginalMap);
virtual void FindOverlappingCorners(FOverlappingCorners& OutOverlappingCorners, const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, float ComparisonThreshold) const override;
void FindOverlappingCorners(FOverlappingCorners& OutOverlappingCorners, FRawMesh const& RawMesh, float ComparisonThreshold) const;
// IModuleInterface interface.
virtual void StartupModule() override;
virtual void ShutdownModule() override;
virtual void ExtractMeshDataForGeometryCache(FRawMesh& RawMesh, const FMeshBuildSettings& BuildSettings, TArray<FStaticMeshBuildVertex>& OutVertices, TArray<TArray<uint32>>& OutPerSectionIndices, int32 ImportVersion);
//virtual void CalculateTextureCoordinateBoundsForSkeletalMesh(const FSkeletalMeshLODModel& LODModel, TArray<FBox2D>& OutBounds) const override;
//virtual bool GenerateUniqueUVsForSkeletalMesh(const FSkeletalMeshLODModel& LODModel, int32 TextureResolution, TArray<FVector2D>& OutTexCoords) const override;
virtual bool RemoveBonesFromMesh(USkeletalMesh* SkeletalMesh, int32 LODIndex, const TArray<FName>* BoneNamesToRemove) const override;
virtual void 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 override;
virtual void CalculateMikkTSpaceTangents(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, const TArray<FVector2D>& InUVs, const TArray<FVector>& InNormals, bool bIgnoreDegenerateTriangles, TArray<FVector>& OutTangentX,
TArray<FVector>& OutTangentY) const override;
//virtual void CalculateNormals(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, const TArray<FVector2D>& InUVs, const TArray<uint32>& InSmoothingGroupIndices, const uint32 InTangentOptions, TArray<FVector>& OutNormals) const override;
virtual void CalculateOverlappingCorners(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, bool bIgnoreDegenerateTriangles, FOverlappingCorners& OutOverlappingCorners) const override;
//virtual void GenerateRuntimeSkinWeightData(const FSkeletalMeshLODModel* ImportedModel, const TArray<FRawSkinWeight>& InRawSkinWeights, FRuntimeSkinWeightProfileData& InOutSkinWeightOverrideData) const override;
void RegisterMenus();
// Need to call some members from this class, (which is internal to this module)
friend class FStaticMeshUtilityBuilder;
protected:
void AddAnimationBlueprintEditorToolbarExtender();
void RemoveAnimationBlueprintEditorToolbarExtender();
//TSharedRef<FExtender> GetAnimationBlueprintEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<IAnimationBlueprintEditor> InAnimationBlueprintEditor);
void AddAnimationEditorToolbarExtender();
void RemoveAnimationEditorToolbarExtender();
//TSharedRef<FExtender> GetAnimationEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<IAnimationEditor> InAnimationEditor);
//TSharedRef<FExtender> GetSkeletalMeshEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<ISkeletalMeshEditor> InSkeletalMeshEditor);
void AddSkeletonEditorToolbarExtender();
void RemoveSkeletonEditorToolbarExtender();
//TSharedRef<FExtender> GetSkeletonEditorToolbarExtender(const TSharedRef<FUICommandList> CommandList, TSharedRef<ISkeletonEditor> InSkeletonEditor);
void HandleAddSkeletalMeshActionExtenderToToolbar(FToolBarBuilder& ParentToolbarBuilder, UMeshComponent* MeshComponent);
void AddLevelViewportMenuExtender();
void RemoveLevelViewportMenuExtender();
TSharedRef<FExtender> GetLevelViewportContextMenuExtender(const TSharedRef<FUICommandList> CommandList, const TArray<AActor*> InActors);
void ConvertActorMeshesToStaticMeshUIAction(const TArray<AActor*> InActors);
FDelegateHandle ModuleLoadedDelegateHandle;
FDelegateHandle LevelViewportExtenderHandle;
FDelegateHandle AnimationBlueprintEditorExtenderHandle;
FDelegateHandle AnimationEditorExtenderHandle;
FDelegateHandle SkeletonEditorExtenderHandle;
};
DECLARE_LOG_CATEGORY_EXTERN(LogMeshUtilities, Verbose, All);
namespace MeshUtilities
{
/** Generates unit length, stratified and uniformly distributed direction samples in a hemisphere. */
void GenerateStratifiedUniformHemisphereSamples(int32 NumSamples, FRandomStream& RandomStream, TArray<FVector4>& Samples);
};

View File

@@ -0,0 +1,211 @@
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
DistanceFieldAtlas.h
=============================================================================*/
#pragma once
#include "CoreMinimal.h"
#include "Containers/LockFreeList.h"
#include "ProfilingDebugging/ResourceSize.h"
#include "Engine/EngineTypes.h"
#include "UObject/GCObject.h"
#include "Templates/UniquePtr.h"
#include "DerivedMeshDataTaskUtils.h"
#include "Async/AsyncWork.h"
class MESHUTILITIES2_API FSignedDistanceFieldBuildMaterialData2
{
public:
EBlendMode BlendMode;
bool bTwoSided;
};
class UStaticMesh;
class UTexture2D;
template <class T>
class TLockFreePointerListLIFO;
// Change DDC key when modifying these (or any DF encoding logic)
namespace DistanceField2
{
// One voxel border around object for handling gradient
constexpr int32 MeshDistanceFieldObjectBorder = 1;
constexpr int32 UniqueDataBrickSize = 7;
// Half voxel border around brick for trilinear filtering
constexpr int32 BrickSize = 8;
// Trade off between SDF memory and number of steps required to find intersection
constexpr int32 BandSizeInVoxels = 4;
constexpr int32 NumMips = 3;
constexpr uint32 InvalidBrickIndex = 0xFFFFFFFF;
constexpr EPixelFormat DistanceFieldFormat = PF_G8;
// Must match LoadDFAssetData
constexpr uint32 MaxIndirectionDimension = 1024;
};
class FSparseDistanceFieldMip2
{
public:
FSparseDistanceFieldMip2() :
IndirectionDimensions(FIntVector::ZeroValue),
NumDistanceFieldBricks(0),
VolumeToVirtualUVScale(FVector::ZeroVector),
VolumeToVirtualUVAdd(FVector::ZeroVector),
DistanceFieldToVolumeScaleBias(FVector2D::ZeroVector),
BulkOffset(0),
BulkSize(0)
{
}
FIntVector IndirectionDimensions;
int32 NumDistanceFieldBricks;
FVector VolumeToVirtualUVScale;
FVector VolumeToVirtualUVAdd;
FVector2D DistanceFieldToVolumeScaleBias;
uint32 BulkOffset;
uint32 BulkSize;
friend FArchive& operator<<(FArchive& Ar, FSparseDistanceFieldMip2& Mip)
{
Ar << Mip.IndirectionDimensions << Mip.NumDistanceFieldBricks << Mip.VolumeToVirtualUVScale << Mip.VolumeToVirtualUVAdd << Mip.DistanceFieldToVolumeScaleBias << Mip.BulkOffset << Mip.BulkSize;
return Ar;
}
SIZE_T GetResourceSizeBytes() const
{
FResourceSizeEx ResSize;
GetResourceSizeEx(ResSize);
return ResSize.GetTotalMemoryBytes();
}
void GetResourceSizeEx(FResourceSizeEx& CumulativeResourceSize) const
{
CumulativeResourceSize.AddDedicatedSystemMemoryBytes(sizeof(*this));
}
};
class FAsyncDistanceFieldTask2;
class FAsyncDistanceFieldTaskWorker2 : public FNonAbandonableTask
{
public:
FAsyncDistanceFieldTaskWorker2(FAsyncDistanceFieldTask2& InTask)
: Task(InTask)
{
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FAsyncDistanceFieldTaskWorker2, STATGROUP_ThreadPoolAsyncTasks);
}
void DoWork();
private:
FAsyncDistanceFieldTask2& Task;
};
/** A task to build a distance field for a single mesh */
class MESHUTILITIES2_API FAsyncDistanceFieldTask2
{
public:
FAsyncDistanceFieldTask2();
// #if WITH_EDITOR
TArray<FSignedDistanceFieldBuildMaterialData2> MaterialBlendModes;
// #endif
FSourceMeshDataForDerivedDataTask SourceMeshData;
UStaticMesh* StaticMesh;
UStaticMesh* GenerateSource;
float DistanceFieldResolutionScale;
bool bGenerateDistanceFieldAsIfTwoSided;
const ITargetPlatform* TargetPlatform;
FString DDCKey;
FDistanceFieldVolumeData* GeneratedVolumeData;
TUniquePtr<FAsyncTask<FAsyncDistanceFieldTaskWorker2>> AsyncTask = nullptr;
};
/** Class that manages asynchronous building of mesh distance fields. */
class MESHUTILITIES2_API FDistanceFieldAsyncQueue2 : public FGCObject
{
public:
FDistanceFieldAsyncQueue2();
virtual ~FDistanceFieldAsyncQueue2();
/** Adds a new build task. (Thread-Safe) */
void AddTask(FAsyncDistanceFieldTask2* Task);
/** Cancel the build on this specific static mesh or block until it is completed if already started. */
void CancelBuild(UStaticMesh* StaticMesh);
/** Blocks the main thread until the async build are either cancelled or completed. */
void CancelAllOutstandingBuilds();
/** Blocks the main thread until the async build of the specified mesh is complete. */
void BlockUntilBuildComplete(UStaticMesh* StaticMesh, bool bWarnIfBlocked);
/** Blocks the main thread until all async builds complete. */
void BlockUntilAllBuildsComplete();
/** Called once per frame, fetches completed tasks and applies them to the scene. */
void ProcessAsyncTasks(bool bLimitExecutionTime = false);
/** Exposes UObject references used by the async build. */
virtual void AddReferencedObjects(FReferenceCollector& Collector) override;
/** Returns name of class for reference tracking */
virtual FString GetReferencerName() const override;
/** Blocks until it is safe to shut down (worker threads are idle). */
void Shutdown();
int32 GetNumOutstandingTasks() const
{
FScopeLock Lock(&CriticalSection);
return ReferencedTasks.Num();
}
private:
friend FAsyncDistanceFieldTaskWorker2;
void ProcessPendingTasks();
TUniquePtr<FQueuedThreadPool> ThreadPool;
/** Builds a single task with the given threadpool. Called from the worker thread. */
void Build(FAsyncDistanceFieldTask2* Task, class FQueuedThreadPool& ThreadPool);
/** Change the priority of the background task. */
void RescheduleBackgroundTask(FAsyncDistanceFieldTask2* InTask, EQueuedWorkPriority InPriority);
/** Task will be sent to a background worker. */
void StartBackgroundTask(FAsyncDistanceFieldTask2* Task);
/** Cancel or finish any background work for the given task. */
void CancelBackgroundTask(TArray<FAsyncDistanceFieldTask2*> Tasks);
/** Game-thread managed list of tasks in the async system. */
TArray<FAsyncDistanceFieldTask2*> ReferencedTasks;
/** Tasks that are waiting on static mesh compilation to proceed */
TArray<FAsyncDistanceFieldTask2*> PendingTasks;
/** Tasks that have completed processing. */
// consider changing this from FIFO to Unordered, which may be faster
TLockFreePointerListLIFO<FAsyncDistanceFieldTask2> CompletedTasks;
class IMeshUtilities2* MeshUtilities;
mutable FCriticalSection CriticalSection;
};
/** Global build queue. */
extern MESHUTILITIES2_API FDistanceFieldAsyncQueue2* GDistanceFieldAsyncQueue2;
extern MESHUTILITIES2_API FString BuildDistanceFieldDerivedDataKey2(const FString& InMeshKey);
extern MESHUTILITIES2_API void BuildMeshDistanceField(UStaticMesh* StaticMesh);
extern MESHUTILITIES2_API void BuildMeshCardRepresentation(UStaticMesh* StaticMeshAsset, class FStaticMeshRenderData& RenderData, FSourceMeshDataForDerivedDataTask* OptionalSourceMeshData);

View File

@@ -0,0 +1,133 @@
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
MeshCardRepresentation.h
=============================================================================*/
#pragma once
#include "CoreMinimal.h"
#include "Containers/LockFreeList.h"
#include "UObject/GCObject.h"
#include "Templates/UniquePtr.h"
#include "DerivedMeshDataTaskUtils.h"
#include "DistanceFieldAtlas2.h"
#include "Async/AsyncWork.h"
template <class T>
class TLockFreePointerListLIFO;
class FSignedDistanceFieldBuildMaterialData;
class FAsyncCardRepresentationTask2;
class FAsyncCardRepresentationTaskWorker2 : public FNonAbandonableTask
{
public:
FAsyncCardRepresentationTaskWorker2(FAsyncCardRepresentationTask2& InTask)
: Task(InTask)
{
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FAsyncCardRepresentationTaskWorker2, STATGROUP_ThreadPoolAsyncTasks);
}
void DoWork();
private:
FAsyncCardRepresentationTask2& Task;
};
class FAsyncCardRepresentationTask2
{
public:
bool bSuccess = false;
// #if WITH_EDITOR
TArray<FSignedDistanceFieldBuildMaterialData2> MaterialBlendModes;
// #endif
FSourceMeshDataForDerivedDataTask SourceMeshData;
bool bGenerateDistanceFieldAsIfTwoSided = false;
UStaticMesh* StaticMesh = nullptr;
UStaticMesh* GenerateSource = nullptr;
FString DDCKey;
FCardRepresentationData* GeneratedCardRepresentation;
TUniquePtr<FAsyncTask<FAsyncCardRepresentationTaskWorker2>> AsyncTask = nullptr;
};
/** Class that manages asynchronous building of mesh distance fields. */
class MESHUTILITIES2_API FCardRepresentationAsyncQueue2 : public FGCObject
{
public:
FCardRepresentationAsyncQueue2();
virtual ~FCardRepresentationAsyncQueue2() override;
/** Adds a new build task. */
void AddTask(FAsyncCardRepresentationTask2* Task);
/** Cancel the build on this specific static mesh or block until it is completed if already started. */
void CancelBuild(UStaticMesh* StaticMesh);
/** Blocks the main thread until the async build are either cancelled or completed. */
void CancelAllOutstandingBuilds();
/** Blocks the main thread until the async build of the specified mesh is complete. */
void BlockUntilBuildComplete(UStaticMesh* StaticMesh, bool bWarnIfBlocked);
/** Blocks the main thread until all async builds complete. */
void BlockUntilAllBuildsComplete();
/** Called once per frame, fetches completed tasks and applies them to the scene. */
void ProcessAsyncTasks(bool bLimitExecutionTime = false);
/** Exposes UObject references used by the async build. */
void AddReferencedObjects(FReferenceCollector& Collector);
virtual FString GetReferencerName() const override;
/** Blocks until it is safe to shut down (worker threads are idle). */
void Shutdown();
int32 GetNumOutstandingTasks() const
{
FScopeLock Lock(&CriticalSection);
return ReferencedTasks.Num();
}
private:
friend FAsyncCardRepresentationTaskWorker2;
void ProcessPendingTasks();
TUniquePtr<FQueuedThreadPool> ThreadPool;
/** Builds a single task with the given threadpool. Called from the worker thread. */
void Build(FAsyncCardRepresentationTask2* Task, class FQueuedThreadPool& ThreadPool);
/** Change the priority of the background task. */
void RescheduleBackgroundTask(FAsyncCardRepresentationTask2* InTask, EQueuedWorkPriority InPriority);
/** Task will be sent to a background worker. */
void StartBackgroundTask(FAsyncCardRepresentationTask2* Task);
/** Cancel or finish any background work for the given task. */
void CancelBackgroundTask(TArray<FAsyncCardRepresentationTask2*> Tasks);
/** Game-thread managed list of tasks in the async system. */
TArray<FAsyncCardRepresentationTask2*> ReferencedTasks;
/** Tasks that are waiting on static mesh compilation to proceed */
TArray<FAsyncCardRepresentationTask2*> PendingTasks;
/** Tasks that have completed processing. */
TLockFreePointerListLIFO<FAsyncCardRepresentationTask2> CompletedTasks;
class IMeshUtilities2* MeshUtilities;
mutable FCriticalSection CriticalSection;
};
/** Global build queue. */
extern MESHUTILITIES2_API FCardRepresentationAsyncQueue2* GCardRepresentationAsyncQueue2;

View File

@@ -0,0 +1,410 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleInterface.h"
#include "Components.h"
#include "Engine/MeshMerging.h"
// #include "SkelImport.h"
#include "DistanceFieldAtlas2.h"
#include "MeshBuild.h"
#include "IMeshMergeUtilities.h"
class UMeshComponent;
class USkeletalMesh;
class UStaticMesh;
class UStaticMeshComponent;
struct FFlattenMaterial;
struct FRawMesh;
struct FStaticMeshLODResources;
class FSourceMeshDataForDerivedDataTask;
typedef FIntPoint FMeshIdAndLOD;
struct FFlattenMaterial;
struct FReferenceSkeleton;
struct FStaticMeshLODResources;
class UMeshComponent;
class UStaticMesh;
namespace ETangentOptions2
{
enum Type
{
None = 0,
BlendOverlappingNormals = 0x1,
IgnoreDegenerateTriangles = 0x2,
UseMikkTSpace = 0x4,
};
};
/**
* Contains the vertices that are most dominated by that bone. Vertices are in Bone space.
* Not used at runtime, but useful for fitting physics assets etc.
*/
struct FBoneVertInfo2
{
// Invariant: Arrays should be same length!
TArray<FVector> Positions;
TArray<FVector> Normals;
};
struct FOverlappingCorners;
class IMeshUtilities2 : public IModuleInterface
{
public:
/************************************************************************/
/* DEPRECATED FUNCTIONALITY */
/************************************************************************/
/**
* Harvest static mesh components from input actors
* and merge into signle mesh grouping them by unique materials
*
* @param SourceActors List of actors to merge
* @param InSettings Settings to use
* @param InOuter Outer if required
* @param InBasePackageName Destination package name for a generated assets. Used if Outer is null.
* @param UseLOD -1 if you'd like to build for all LODs. If you specify, that LOD mesh for source meshes will be used to merge the mesh
* This is used by hierarchical building LODs
* @param OutAssetsToSync Merged mesh assets
* @param OutMergedActorLocation World position of merged mesh
*/
// virtual void MergeActors(
// const TArray<AActor*>& SourceActors,
// const FMeshMergingSettings& InSettings,
// UPackage* InOuter,
// const FString& InBasePackageName,
// TArray<UObject*>& OutAssetsToSync,
// FVector& OutMergedActorLocation,
// bool bSilent=false) const = 0;
/**
* MergeStaticMeshComponents
*
* @param ComponentsToMerge - Components to merge
* @param World - World in which the component reside
* @param InSettings - Settings to use
* @param InOuter - Outer if required
* @param InBasePackageName - Destination package name for a generated assets. Used if Outer is null.
* @param UseLOD -1 if you'd like to build for all LODs. If you specify, that LOD mesh for source meshes will be used to merge the mesh
* This is used by hierarchical building LODs
* @param OutAssetsToSync Merged mesh assets
* @param OutMergedActorLocation World position of merged mesh
* @param ViewDistance Distance for LOD determination
* @param bSilent Non-verbose flag
* @return void
*/
// virtual void MergeStaticMeshComponents(
// const TArray<UStaticMeshComponent*>& ComponentsToMerge,
// UWorld* World,
// const FMeshMergingSettings& InSettings,
// UPackage* InOuter,
// const FString& InBasePackageName,
// TArray<UObject*>& OutAssetsToSync,
// FVector& OutMergedActorLocation,
// const float ScreenAreaSize,
// bool bSilent /*= false*/) const = 0;
/**
* Creates a (proxy)-mesh combining the static mesh components from the given list of actors (at the moment this requires having Simplygon)
*
* @param InActors - List of Actors to merge
* @param InMeshProxySettings - Merge settings
* @param InOuter - Package for a generated assets, if NULL new packages will be created for each asset
* @param InProxyBasePackageName - Will be used for naming generated assets, in case InOuter is not specified ProxyBasePackageName will be used as long package name for creating new packages
* @param InGuid - Guid identifying the data used for this proxy job
* @param InProxyCreatedDelegate - Delegate callback for when the proxy is finished
* @param bAllowAsync - Flag whether or not this call could be run async (SimplygonSwarm)
*/
// virtual void CreateProxyMesh(const TArray<class AActor*>& InActors, const struct FMeshProxySettings& InMeshProxySettings, UPackage* InOuter, const FString& InProxyBasePackageName, const FGuid InGuid, FCreateProxyDelegate InProxyCreatedDelegate, const bool bAllowAsync = false, const float ScreenAreaSize = 1.0f) = 0;
/**
* FlattenMaterialsWithMeshData
*
* @param InMaterials - List of unique materials used by InSourceMeshes
* @param InSourceMeshes - List of raw meshes used to flatten the materials with (vertex data)
* @param InMaterialIndexMap - Map used for mapping the raw meshes to the correct materials
* @param InMeshShouldBakeVertexData - Array of flags to determine whether or not a mesh requires to have its vertex data baked down
* @param InMaterialProxySettings - Settings for creating the flattened material
* @param OutFlattenedMaterials - List of flattened materials (one for each mesh)
*/
//virtual void FlattenMaterialsWithMeshData(TArray<UMaterialInterface*>& InMaterials, TArray<struct FRawMeshExt>& InSourceMeshes, TMap<FMeshIdAndLOD, TArray<int32>>& InMaterialIndexMap, TArray<bool>& InMeshShouldBakeVertexData, const FMaterialProxySettings &InMaterialProxySettings, TArray<FFlattenMaterial> &OutFlattenedMaterials) const = 0;
/**
* Calculates (new) non-overlapping UV coordinates for the given Raw Mesh
*
* @param RawMesh - Raw Mesh to generate UV coordinates for
* @param TextureResolution - Texture resolution to take into account while generating the UVs
* @param bMergeIdenticalMaterials - Whether faces with identical materials can be treated as one in the resulting set of unique UVs
* @param OutTexCoords - New set of UV coordinates
* @return bool - whether or not generating the UVs succeeded
*/
//virtual bool GenerateUniqueUVsForStaticMesh(const FRawMesh& RawMesh, int32 TextureResolution, TArray<FVector2D>& OutTexCoords) const = 0;
//virtual bool GenerateUniqueUVsForStaticMesh(const FRawMesh& RawMesh, int32 TextureResolution, bool bMergeIdenticalMaterials, TArray<FVector2D>& OutTexCoords) const = 0;
// /** Returns the mesh reduction plugin if available. */
// virtual IMeshReduction* GetStaticMeshReductionInterface() = 0;
//
// /** Returns the mesh reduction plugin if available. */
// virtual IMeshReduction* GetSkeletalMeshReductionInterface() = 0;
//
// /** Returns the mesh merging plugin if available. */
// virtual IMeshMerging* GetMeshMergingInterface() = 0;
public:
/** Returns a string uniquely identifying this version of mesh utilities. */
virtual const FString& GetVersionString() const = 0;
/** Used to make sure all imported material slot name are unique and non empty.
*
* @param StaticMesh
* @param bForceUniqueSlotName If true, make sure all slot names are unique as well.
*/
virtual void FixupMaterialSlotNames(UStaticMesh* StaticMesh) const = 0;
/** Used to make sure all imported material slot name are unique and non empty.
*
* @param SkeletalMesh
* @param bForceUniqueSlotName If true, make sure all slot names are unique as well.
*/
virtual void FixupMaterialSlotNames(USkeletalMesh* SkeletalMesh) const = 0;
/**
* Builds a renderable static mesh using the provided source models and the LOD groups settings.
* @returns true if the renderable mesh was built successfully.
*/
//virtual bool BuildStaticMesh(
//class FStaticMeshRenderData& OutRenderData,
//UStaticMesh* StaticMesh,
//const class FStaticMeshLODGroup& LODGroup
//) = 0;
virtual void BuildStaticMeshVertexAndIndexBuffers(
TArray<FStaticMeshBuildVertex>& OutVertices,
TArray<TArray<uint32> >& OutPerSectionIndices,
TArray<int32>& OutWedgeMap,
const FRawMesh& RawMesh,
const FOverlappingCorners& OverlappingCorners,
const TMap<uint32, uint32>& MaterialToSectionMapping,
float ComparisonThreshold,
FVector BuildScale,
int32 ImportVersion
) = 0;
/**
* Builds a static mesh using the provided source models and the LOD groups settings, and replaces
* the RawMeshes with the reduced meshes. Does not modify renderable data.
* @returns true if the meshes were built successfully.
*/
// virtual bool GenerateStaticMeshLODs(
// UStaticMesh* StaticMesh,
// const class FStaticMeshLODGroup& LODGroup
// ) = 0;
/** Builds a signed distance field volume for the given LODModel. */
virtual void GenerateSignedDistanceFieldVolumeData(
FString MeshName,
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
const FStaticMeshLODResources& LODModel,
class FQueuedThreadPool& ThreadPool,
const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
const FBoxSphereBounds& Bounds,
float DistanceFieldResolutionScale,
bool bGenerateAsIfTwoSided,
class FDistanceFieldVolumeData& OutData) = 0;
virtual bool GenerateCardRepresentationData(
FString MeshName,
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
const FStaticMeshLODResources& LODModel,
class FQueuedThreadPool& ThreadPool,
const TArray<FSignedDistanceFieldBuildMaterialData2>& MaterialBlendModes,
const FBoxSphereBounds& Bounds,
const class FDistanceFieldVolumeData* DistanceFieldVolumeData,
bool bGenerateAsIfTwoSided,
class FCardRepresentationData& OutData) = 0;
/** Helper structure for skeletal mesh import options */
struct MeshBuildOptions
{
MeshBuildOptions()
: bRemoveDegenerateTriangles(false)
, bComputeNormals(true)
, bComputeTangents(true)
, bUseMikkTSpace(false)
, bComputeWeightedNormals(false)
{
}
bool bRemoveDegenerateTriangles;
bool bComputeNormals;
bool bComputeTangents;
bool bUseMikkTSpace;
bool bComputeWeightedNormals;
FOverlappingThresholds OverlappingThresholds;
void FillOptions(const FSkeletalMeshBuildSettings& SkeletalMeshBuildSettings)
{
OverlappingThresholds.ThresholdPosition = SkeletalMeshBuildSettings.ThresholdPosition;
OverlappingThresholds.ThresholdTangentNormal = SkeletalMeshBuildSettings.ThresholdTangentNormal;
OverlappingThresholds.ThresholdUV = SkeletalMeshBuildSettings.ThresholdUV;
OverlappingThresholds.MorphThresholdPosition = SkeletalMeshBuildSettings.MorphThresholdPosition;
bComputeNormals = SkeletalMeshBuildSettings.bRecomputeNormals;
bComputeTangents = SkeletalMeshBuildSettings.bRecomputeTangents;
bUseMikkTSpace = SkeletalMeshBuildSettings.bUseMikkTSpace;
bComputeWeightedNormals = SkeletalMeshBuildSettings.bComputeWeightedNormals;
bRemoveDegenerateTriangles = SkeletalMeshBuildSettings.bRemoveDegenerates;
}
};
// /**
// * Create all render specific data for a skeletal mesh LOD model
// * @returns true if the mesh was built successfully.
// */
// virtual bool BuildSkeletalMesh(
// FSkeletalMeshLODModel& LODModel,
// const FString& SkeletalMeshName,
// 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 = MeshBuildOptions(),
// TArray<FText> * OutWarningMessages = NULL,
// TArray<FName> * OutWarningNames = NULL
// ) = 0;
/** Cache optimize the index buffer. */
virtual void CacheOptimizeIndexBuffer(TArray<uint16>& Indices) = 0;
/** Cache optimize the index buffer. */
virtual void CacheOptimizeIndexBuffer(TArray<uint32>& Indices) = 0;
/** Build adjacency information for the skeletal mesh used for tessellation. */
virtual void BuildSkeletalAdjacencyIndexBuffer(
const TArray<struct FSoftSkinVertex>& VertexBuffer,
const uint32 TexCoordCount,
const TArray<uint32>& Indices,
TArray<uint32>& OutPnAenIndices
) = 0;
/**
* Calculate The tangent, bi normal and normal for the triangle define by the tree SoftSkinVertex.
*
* @note The function will always fill properly the OutTangents array with 3 FVector. If the triangle is degenerated the OutTangent will contain zeroed vectors.
*
* @param VertexA - First triangle vertex.
* @param VertexB - Second triangle vertex.
* @param VertexC - Third triangle vertex.
* @param OutTangents - The function allocate the TArray with 3 FVector, to represent the triangle tangent, bi normal and normal.
* @param CompareThreshold - The threshold use to compare a tangent vector with zero.
*/
virtual void CalculateTriangleTangent(const FSoftSkinVertex& VertexA, const FSoftSkinVertex& VertexB, const FSoftSkinVertex& VertexC, TArray<FVector>& OutTangents, float CompareThreshold) = 0;
/**
* Calculate the verts associated weighted to each bone of the skeleton.
* The vertices returned are in the local space of the bone.
*
* @param SkeletalMesh The target skeletal mesh.
* @param Infos The output array of vertices associated with each bone.
* @param bOnlyDominant Controls whether a vertex is added to the info for a bone if it is most controlled by that bone, or if that bone has ANY influence on that vert.
*/
virtual void CalcBoneVertInfos( USkeletalMesh* SkeletalMesh, TArray<FBoneVertInfo2>& Infos, bool bOnlyDominant) = 0;
/**
* Convert a set of mesh components in their current pose to a static mesh.
* @param InMeshComponents The mesh components we want to convert
* @param InRootTransform The transform of the root of the mesh we want to output
* @param InPackageName The package name to create the static mesh in. If this is empty then a dialog will be displayed to pick the mesh.
* @return a new static mesh (specified by the user)
*/
virtual UStaticMesh* ConvertMeshesToStaticMesh(const TArray<UMeshComponent*>& InMeshComponents, const FTransform& InRootTransform = FTransform::Identity, const FString& InPackageName = FString()) = 0;
/**
* Calculates UV coordinates bounds for the given Skeletal Mesh
*
* @param InRawMesh - Skeletal Mesh to calculate the bounds for
* @param OutBounds - Out texture bounds (min-max)
*/
//virtual void CalculateTextureCoordinateBoundsForSkeletalMesh(const FSkeletalMeshLODModel& LODModel, TArray<FBox2D>& OutBounds) const = 0;
/** Calculates (new) non-overlapping UV coordinates for the given Skeletal Mesh
*
* @param LODModel - Skeletal Mesh to generate UV coordinates for
* @param TextureResolution - Texture resolution to take into account while generating the UVs
* @param OutTexCoords - New set of UV coordinates
* @return bool - whether or not generating the UVs succeeded
*/
//virtual bool GenerateUniqueUVsForSkeletalMesh(const FSkeletalMeshLODModel& LODModel, int32 TextureResolution, TArray<FVector2D>& OutTexCoords) const = 0;
/**
* Remove Bones based on LODInfo setting
*
* @param SkeletalMesh Mesh that needs bones to be removed
* @param LODIndex Desired LOD to remove bones [ 0 based ]
* @param BoneNamesToRemove List of bone names to remove
*
* @return true if success
*/
virtual bool RemoveBonesFromMesh(USkeletalMesh* SkeletalMesh, int32 LODIndex, const TArray<FName>* BoneNamesToRemove) const = 0;
/**
* Calculates Tangents and Normals for a given set of vertex data
*
* @param InVertices Vertices that make up the mesh
* @param InIndices Indices for the Vertex array
* @param InUVs Texture coordinates (per-index based)
* @param InSmoothingGroupIndices Smoothing group index (per-face based)
* @param InTangentOptions Flags for Tangent calculation
* @param OutTangentX Array to hold calculated Tangents
* @param OutTangentY Array to hold calculated Bitangents
* @param OutNormals Array to hold calculated normals (if already contains normals will use those instead for the tangent calculation)
*/
virtual void 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 = 0;
/**
* Calculates MikkTSpace Tangents for a given set of vertex data with normals provided
*
* @param InVertices Vertices that make up the mesh
* @param InIndices Indices for the Vertex array
* @param InUVs Texture coordinates (per-index based)
* @param InNormals Normals used for the tangent calculation (must be normalized)
* @param bIgnoreDegenerateTriangles Flag for MikkTSpace to skip degenerate triangles fix-up path
* @param OutTangentX Array to hold calculated Tangents
* @param OutTangentY Array to hold calculated Bitangents
*/
virtual void CalculateMikkTSpaceTangents(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, const TArray<FVector2D>& InUVs, const TArray<FVector>& InNormals, bool bIgnoreDegenerateTriangles, TArray<FVector>& OutTangentX, TArray<FVector>& OutTangentY) const = 0;
/**
* Calculates Normals for a given set of vertex data
*
* @param InVertices Vertices that make up the mesh
* @param InIndices Indices for the Vertex array
* @param InUVs Texture coordinates (per-index based)
* @param InSmoothingGroupIndices Smoothing group index (per-face based)
* @param InTangentOptions Flags for Tangent calculation
* @param OutNormals Array to hold calculated normals
*/
//virtual void CalculateNormals(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, const TArray<FVector2D>& InUVs, const TArray<uint32>& InSmoothingGroupIndices, const uint32 InTangentOptions, TArray<FVector>& OutNormals) const = 0;
/**
* Calculates the overlapping corners for a given set of vertex data
*
* @param InVertices Vertices that make up the mesh
* @param InIndices Indices for the Vertex array
* @param bIgnoreDegenerateTriangles Indicates if we should skip degenerate triangles
* @param OutOverlappingCorners Container to hold the overlapping corners. For a vertex, lists all the overlapping vertices.
*/
virtual void CalculateOverlappingCorners(const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, bool bIgnoreDegenerateTriangles, FOverlappingCorners& OutOverlappingCorners) const = 0;
virtual void RecomputeTangentsAndNormalsForRawMesh(bool bRecomputeTangents, bool bRecomputeNormals, const FMeshBuildSettings& InBuildSettings, FRawMesh &OutRawMesh) const = 0;
virtual void RecomputeTangentsAndNormalsForRawMesh(bool bRecomputeTangents, bool bRecomputeNormals, const FMeshBuildSettings& InBuildSettings, const FOverlappingCorners& InOverlappingCorners, FRawMesh &OutRawMesh) const = 0;
virtual void FindOverlappingCorners(FOverlappingCorners& OutOverlappingCorners, const TArray<FVector>& InVertices, const TArray<uint32>& InIndices, float ComparisonThreshold) const = 0;
/** Used to generate runtime skin weight data from Editor-only data */
// virtual void GenerateRuntimeSkinWeightData(const FSkeletalMeshLODModel* ImportedModel, const TArray<FRawSkinWeight>& InRawSkinWeights, struct FRuntimeSkinWeightProfileData& InOutSkinWeightOverrideData) const = 0;
};