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

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
Binaries
.vs
.idea
.history
node_modules
Intermediate
DerivedDataCache
Build
Saved
/Content/JavaScript
/Content/Typing
/Content/TypeScript
/Content/Developers
/Content/Development
/Content/Data
/Content/project_ok
/Content/Blueprints
ArtistRenderer.sln
*.pyc
package-lock.json
/dpaks

11
B.bat Normal file
View File

@ -0,0 +1,11 @@
@echo off
rem 编译Shipping版本,打包pak
set BuildConfig="Shipping"
set UprojectPath="%~dp0%wjj%.uproject"
set DebugPath="%~dp0Build/%BuildConfig%"
D:\UnrealEngine-5.0.0-early-access-2\Engine\Build\BatchFiles\RunUAT.bat BuildCookRun -project=%UprojectPath%^
-nocompileeditor^
-noP4 -platform=Win64^
-clientconfig=%BuildConfig%^
-cook -allmaps -build -stage^
-pak -stage -archive -archivedirectory="%~dp0/Build/%BuildConfig%"

1
Config/DefaultEditor.ini Normal file
View File

@ -0,0 +1 @@

43
Config/DefaultEngine.ini Normal file
View File

@ -0,0 +1,43 @@
[/Script/EngineSettings.GameMapsSettings]
GameDefaultMap=/Game/NewMap.NewMap
EditorStartupMap=/Game/NewMap.NewMap
GameInstanceClass=/Script/SMC_Build.SMCGameInstance
[/Script/HardwareTargeting.HardwareTargetingSettings]
TargetedHardwareClass=Desktop
AppliedTargetedHardwareClass=Desktop
DefaultGraphicsPerformance=Maximum
AppliedDefaultGraphicsPerformance=Maximum
[/Script/Engine.RendererSettings]
r.GenerateMeshDistanceFields=True
r.DynamicGlobalIlluminationMethod=1
r.ReflectionMethod=1
r.SkinCache.CompileShaders=True
r.RayTracing=True
r.Shadow.Virtual.Enable=1
r.Lumen.HardwareRayTracing=True
r.MinScreenRadiusForLights=500.000000
r.RayTracing.Shadows=True
r.AllowStaticLighting=False
r.DefaultFeature.LightUnits=2
r.DefaultFeature.AutoExposure=False
[/Script/WindowsTargetPlatform.WindowsTargetSettings]
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
[/Script/WorldPartitionEditor.WorldPartitionEditorSettings]
bEnableWorldPartition=False
bEnableConversionPrompt=True
CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet
[/Script/Engine.Engine]
+ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/SMC_Build")
+ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/SMC_Build")
+ActiveClassRedirects=(OldClassName="TP_BlankGameModeBase",NewClassName="SMC_BuildGameModeBase")

7
Config/DefaultGame.ini Normal file
View File

@ -0,0 +1,7 @@
[/Script/EngineSettings.GeneralProjectSettings]
ProjectID=DFD3A2844A4CA1EAF134B5968BDC572F
[/Script/UnrealEd.ProjectPackagingSettings]
IncludeDebugFiles=True

BIN
Content/NewMap.umap Normal file

Binary file not shown.

BIN
Content/NewMap1.umap Normal file

Binary file not shown.

Binary file not shown.

BIN
Content/UV_Grid_Sm.uasset Normal file

Binary file not shown.

Binary file not shown.

BIN
Content/aaa.uasset Normal file

Binary file not shown.

BIN
Content/baise.uasset Normal file

Binary file not shown.

BIN
Content/file__1_.uasset Normal file

Binary file not shown.

BIN
Content/webcad.uasset Normal file

Binary file not shown.

17
D.bat Normal file
View File

@ -0,0 +1,17 @@
@echo off
set "lj=%~p0"
set "lj=%lj:\= %"
for %%a in (%lj%) do set wjj=%%a
rem 编译DebugGame的版本,跳过烘焙,打包pak
set BuildConfig=DebugGame
set UprojectPath="%~dp0%wjj%.uproject"
set DebugPath="%~dp0Build\%BuildConfig%"
D:\UnrealEngine-5.0.0-early-access-2\Engine\Build\BatchFiles\RunUAT.bat BuildCookRun -project=%UprojectPath%^
-nocompileeditor^
-noP4 -platform=Win64^
-clientconfig=%BuildConfig%^
-build -utf8output -compile -ForceDebugInfo^
-SkipCook^
-pak -stage -archive -archivedirectory="%DebugPath%"

16
DC.bat Normal file
View File

@ -0,0 +1,16 @@
@echo off
set "lj=%~p0"
set "lj=%lj:\= %"
for %%a in (%lj%) do set wjj=%%a
rem 编译DebugGame的版本,跳过烘焙,打包pak
set BuildConfig=DebugGame
set UprojectPath="%~dp0%wjj%.uproject"
set DebugPath="%~dp0Build\%BuildConfig%"
D:\UnrealEngine-5.0.0-early-access-2\Engine\Build\BatchFiles\RunUAT.bat BuildCookRun -project=%UprojectPath%^
-nocompileeditor^
-noP4 -platform=Win64^
-clientconfig=%BuildConfig%^
-cook -allmaps -build -stage^
-pak -stage -archive -archivedirectory=%DebugPath%

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;
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,42 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "RuntimeGeometryUtils",
"Description": "",
"Category": "Other",
"CreatedBy": "Ryan Schmidt",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": true,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": false,
"Modules": [
{
"Name": "RuntimeGeometryUtils",
"Type": "Runtime",
"LoadingPhase": "Default"
}
],
"Plugins": [
{
"Name": "GeometryProcessing",
"Enabled": true
},
{
"Name": "MeshModelingToolset",
"Enabled": true
},
{
"Name": "ProceduralMeshComponent",
"Enabled": true
},
{
"Name": "MeshUtilities2",
"Enabled": true
}
]
}

View File

@ -0,0 +1,441 @@
#include "DynamicMeshBaseActor.h"
#include "Generators/SphereGenerator.h"
#include "Generators/GridBoxMeshGenerator.h"
#include "MeshQueries.h"
#include "DynamicMesh3.h"
#include "MeshNormals.h"
#include "MeshTransforms.h"
#include "MeshSimplification.h"
#include "Operations/MeshBoolean.h"
#include "Implicit/Solidify.h"
#include "DynamicMeshOBJReader.h"
#include "MeshCardRepresentation2.h"
// Sets default values
ADynamicMeshBaseActor::ADynamicMeshBaseActor()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
AccumulatedTime = 0;
MeshAABBTree.SetMesh(&SourceMesh);
FastWinding = MakeUnique<TFastWindingTree<FDynamicMesh3>>(&MeshAABBTree, false);
}
void ADynamicMeshBaseActor::PostLoad()
{
Super::PostLoad();
OnMeshGenerationSettingsModified();
}
void ADynamicMeshBaseActor::PostActorCreated()
{
Super::PostActorCreated();
OnMeshGenerationSettingsModified();
}
// Called when the game starts or when spawned
void ADynamicMeshBaseActor::BeginPlay()
{
Super::BeginPlay();
AccumulatedTime = 0;
OnMeshGenerationSettingsModified();
}
// Called every frame
void ADynamicMeshBaseActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
AccumulatedTime += DeltaTime;
if (bRegenerateOnTick && SourceType == EDynamicMeshActorSourceType::Primitive)
{
OnMeshGenerationSettingsModified();
}
}
#if WITH_EDITOR
void ADynamicMeshBaseActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
OnMeshGenerationSettingsModified();
}
#endif
void ADynamicMeshBaseActor::EditMesh(TFunctionRef<void(FDynamicMesh3&)> EditFunc)
{
EditFunc(SourceMesh);
// update spatial data structures
if (bEnableSpatialQueries || bEnableInsideQueries)
{
MeshAABBTree.Build();
if (bEnableInsideQueries)
{
FastWinding->Build();
}
}
OnMeshEditedInternal();
}
void ADynamicMeshBaseActor::GetMeshCopy(FDynamicMesh3& MeshOut)
{
MeshOut = SourceMesh;
}
const FDynamicMesh3& ADynamicMeshBaseActor::GetMeshRef() const
{
return SourceMesh;
}
void ADynamicMeshBaseActor::OnMeshEditedInternal()
{
OnMeshModified.Broadcast(this);
}
void ADynamicMeshBaseActor::OnMeshGenerationSettingsModified()
{
EditMesh([this](FDynamicMesh3& MeshToUpdate)
{
RegenerateSourceMesh(MeshToUpdate);
});
}
void ADynamicMeshBaseActor::RegenerateSourceMesh(FDynamicMesh3& MeshOut)
{
if (SourceType == EDynamicMeshActorSourceType::Primitive)
{
double UseRadius = (this->MinimumRadius + this->VariableRadius)
+ (this->VariableRadius) * FMathd::Sin(PulseSpeed * AccumulatedTime);
// generate new mesh
if (this->PrimitiveType == EDynamicMeshActorPrimitiveType::Sphere)
{
FSphereGenerator SphereGen;
SphereGen.NumPhi = SphereGen.NumTheta = FMath::Clamp(this->TessellationLevel, 3, 50);
SphereGen.Radius = UseRadius;
MeshOut.Copy(&SphereGen.Generate());
}
else
{
FGridBoxMeshGenerator BoxGen;
int TessLevel = FMath::Clamp(this->TessellationLevel, 2, 50);
BoxGen.EdgeVertices = FIndex3i(TessLevel, TessLevel, TessLevel);
FVector3d BoxExtents = UseRadius * FVector3d::One();
BoxExtents.Z *= BoxDepthRatio;
BoxGen.Box = FOrientedBox3d(FVector3d::Zero(), BoxExtents);
MeshOut.Copy(&BoxGen.Generate());
}
}
else if (SourceType == EDynamicMeshActorSourceType::ImportedMesh)
{
FString UsePath = ImportPath;
if (FPaths::FileExists(UsePath) == false && FPaths::IsRelative(UsePath))
{
UsePath = FPaths::ProjectContentDir() + ImportPath;
}
MeshOut = FDynamicMesh3();
if (! RTGUtils::ReadOBJMesh(UsePath, MeshOut, true, true, true, bReverseOrientation))
{
UE_LOG(LogTemp, Warning, TEXT("Error reading mesh file %s"), *UsePath);
FSphereGenerator SphereGen;
SphereGen.NumPhi = SphereGen.NumTheta = 8;
SphereGen.Radius = this->MinimumRadius;
MeshOut.Copy(&SphereGen.Generate());
}
if (bCenterPivot)
{
MeshTransforms::Translate(MeshOut, -MeshOut.GetBounds().Center());
}
if (ImportScale != 1.0)
{
MeshTransforms::Scale(MeshOut, ImportScale * FVector3d::One(), FVector3d::Zero());
}
}
RecomputeNormals(MeshOut);
}
void ADynamicMeshBaseActor::RecomputeNormals(FDynamicMesh3& MeshOut)
{
if (this->NormalsMode == EDynamicMeshActorNormalsMode::PerVertexNormals)
{
MeshOut.EnableAttributes();
FMeshNormals::InitializeOverlayToPerVertexNormals(MeshOut.Attributes()->PrimaryNormals(), false);
}
else if (this->NormalsMode == EDynamicMeshActorNormalsMode::FaceNormals)
{
MeshOut.EnableAttributes();
FMeshNormals::InitializeOverlayToPerTriangleNormals(MeshOut.Attributes()->PrimaryNormals());
}
}
int ADynamicMeshBaseActor::GetTriangleCount()
{
return SourceMesh.TriangleCount();
}
float ADynamicMeshBaseActor::DistanceToPoint(FVector WorldPoint, FVector& NearestWorldPoint, int& NearestTriangle, FVector& TriBaryCoords)
{
NearestWorldPoint = WorldPoint;
NearestTriangle = -1;
if (bEnableSpatialQueries == false)
{
return TNumericLimits<float>::Max();
}
FTransform3d ActorToWorld(GetActorTransform());
FVector3d LocalPoint = ActorToWorld.InverseTransformPosition((FVector3d)WorldPoint);
double NearDistSqr;
NearestTriangle = MeshAABBTree.FindNearestTriangle(LocalPoint, NearDistSqr);
if (NearestTriangle < 0)
{
return TNumericLimits<float>::Max();
}
FDistPoint3Triangle3d DistQuery = TMeshQueries<FDynamicMesh3>::TriangleDistance(SourceMesh, NearestTriangle, LocalPoint);
NearestWorldPoint = (FVector)ActorToWorld.TransformPosition(DistQuery.ClosestTrianglePoint);
TriBaryCoords = (FVector)DistQuery.TriangleBaryCoords;
return (float)FMathd::Sqrt(NearDistSqr);
}
FVector ADynamicMeshBaseActor::NearestPoint(FVector WorldPoint)
{
if (bEnableSpatialQueries)
{
FTransform3d ActorToWorld(GetActorTransform());
FVector3d LocalPoint = ActorToWorld.InverseTransformPosition((FVector3d)WorldPoint);
return (FVector)ActorToWorld.TransformPosition(MeshAABBTree.FindNearestPoint(LocalPoint));
}
return WorldPoint;
}
bool ADynamicMeshBaseActor::ContainsPoint(FVector WorldPoint, float WindingThreshold)
{
if (bEnableInsideQueries)
{
FTransform3d ActorToWorld(GetActorTransform());
FVector3d LocalPoint = ActorToWorld.InverseTransformPosition((FVector3d)WorldPoint);
return FastWinding->IsInside(LocalPoint, WindingThreshold);
}
return false;
}
bool ADynamicMeshBaseActor::IntersectRay(FVector RayOrigin, FVector RayDirection,
FVector& WorldHitPoint, float& HitDistance, int& NearestTriangle, FVector& TriBaryCoords,
float MaxDistance)
{
if (bEnableSpatialQueries)
{
FTransform3d ActorToWorld(GetActorTransform());
FVector3d WorldDirection(RayDirection);
WorldDirection.Normalize();
FRay3d LocalRay(ActorToWorld.InverseTransformPosition((FVector3d)RayOrigin),
ActorToWorld.InverseTransformNormal(WorldDirection));
IMeshSpatial::FQueryOptions QueryOptions;
if (MaxDistance > 0)
{
QueryOptions.MaxDistance = MaxDistance;
}
NearestTriangle = MeshAABBTree.FindNearestHitTriangle(LocalRay, QueryOptions);
if (SourceMesh.IsTriangle(NearestTriangle))
{
FIntrRay3Triangle3d IntrQuery = TMeshQueries<FDynamicMesh3>::TriangleIntersection(SourceMesh, NearestTriangle, LocalRay);
if (IntrQuery.IntersectionType == EIntersectionType::Point)
{
HitDistance = IntrQuery.RayParameter;
WorldHitPoint = (FVector)ActorToWorld.TransformPosition(LocalRay.PointAt(IntrQuery.RayParameter));
TriBaryCoords = (FVector)IntrQuery.TriangleBaryCoords;
return true;
}
}
}
return false;
}
void ADynamicMeshBaseActor::SubtractMesh(ADynamicMeshBaseActor* OtherMeshActor)
{
BooleanWithMesh(OtherMeshActor, EDynamicMeshActorBooleanOperation::Subtraction);
}
void ADynamicMeshBaseActor::UnionWithMesh(ADynamicMeshBaseActor* OtherMeshActor)
{
BooleanWithMesh(OtherMeshActor, EDynamicMeshActorBooleanOperation::Union);
}
void ADynamicMeshBaseActor::IntersectWithMesh(ADynamicMeshBaseActor* OtherMeshActor)
{
BooleanWithMesh(OtherMeshActor, EDynamicMeshActorBooleanOperation::Intersection);
}
void ADynamicMeshBaseActor::BooleanWithMesh(ADynamicMeshBaseActor* OtherMeshActor, EDynamicMeshActorBooleanOperation Operation)
{
if (ensure(OtherMeshActor) == false) return;
FTransform3d ActorToWorld(GetActorTransform());
FTransform3d OtherToWorld(OtherMeshActor->GetActorTransform());
FDynamicMesh3 OtherMesh;
OtherMeshActor->GetMeshCopy(OtherMesh);
MeshTransforms::ApplyTransform(OtherMesh, OtherToWorld);
MeshTransforms::ApplyTransformInverse(OtherMesh, ActorToWorld);
EditMesh([&](FDynamicMesh3& MeshToUpdate)
{
FDynamicMesh3 ResultMesh;
FMeshBoolean::EBooleanOp ApplyOp = FMeshBoolean::EBooleanOp::Union;
switch (Operation)
{
default:
break;
case EDynamicMeshActorBooleanOperation::Subtraction:
ApplyOp = FMeshBoolean::EBooleanOp::Difference;
break;
case EDynamicMeshActorBooleanOperation::Intersection:
ApplyOp = FMeshBoolean::EBooleanOp::Intersect;
break;
}
FMeshBoolean Boolean(
&MeshToUpdate, FTransform3d::Identity(),
&OtherMesh, FTransform3d::Identity(),
&ResultMesh,
ApplyOp);
Boolean.bPutResultInInputSpace = true;
bool bOK = Boolean.Compute();
if (!bOK)
{
// fill holes
}
RecomputeNormals(ResultMesh);
MeshToUpdate = MoveTemp(ResultMesh);
});
}
bool ADynamicMeshBaseActor::ImportMesh(FString Path, bool bFlipOrientation, bool bRecomputeNormals)
{
FDynamicMesh3 ImportedMesh;
if (!RTGUtils::ReadOBJMesh(Path, ImportedMesh, true, true, true, bFlipOrientation))
{
UE_LOG(LogTemp, Warning, TEXT("Error reading mesh file %s"), *Path);
return false;
}
if (bRecomputeNormals)
{
RecomputeNormals(ImportedMesh);
}
EditMesh([&](FDynamicMesh3& MeshToUpdate)
{
MeshToUpdate = MoveTemp(ImportedMesh);
});
return true;
}
void ADynamicMeshBaseActor::CopyFromMesh(ADynamicMeshBaseActor* OtherMesh, bool bRecomputeNormals)
{
if (! ensure(OtherMesh)) return;
// the part where we generate a new mesh
FDynamicMesh3 TmpMesh;
OtherMesh->GetMeshCopy(TmpMesh);
// apply our normals setting
if (bRecomputeNormals)
{
RecomputeNormals(TmpMesh);
}
// update the mesh
EditMesh([&](FDynamicMesh3& MeshToUpdate)
{
MeshToUpdate = MoveTemp(TmpMesh);
});
}
void ADynamicMeshBaseActor::SolidifyMesh(int VoxelResolution, float WindingThreshold)
{
if (MeshAABBTree.IsValid() == false)
{
MeshAABBTree.Build();
}
if (FastWinding->IsBuilt() == false)
{
FastWinding->Build();
}
// ugh workaround for bug
FDynamicMesh3 CompactMesh;
CompactMesh.CompactCopy(SourceMesh, false, false, false, false);
FDynamicMeshAABBTree3 AABBTree(&CompactMesh, true);
TFastWindingTree<FDynamicMesh3> Winding(&AABBTree, true);
double ExtendBounds = 2.0;
//TImplicitSolidify<FDynamicMesh3> SolidifyCalc(&SourceMesh, &MeshAABBTree, FastWinding.Get());
//SolidifyCalc.SetCellSizeAndExtendBounds(MeshAABBTree.GetBoundingBox(), ExtendBounds, VoxelResolution);
TImplicitSolidify<FDynamicMesh3> SolidifyCalc(&CompactMesh, &AABBTree, &Winding);
SolidifyCalc.SetCellSizeAndExtendBounds(AABBTree.GetBoundingBox(), ExtendBounds, VoxelResolution);
SolidifyCalc.WindingThreshold = WindingThreshold;
SolidifyCalc.SurfaceSearchSteps = 5;
SolidifyCalc.bSolidAtBoundaries = true;
SolidifyCalc.ExtendBounds = ExtendBounds;
FDynamicMesh3 SolidMesh(&SolidifyCalc.Generate());
SolidMesh.EnableAttributes();
RecomputeNormals(SolidMesh);
EditMesh([&](FDynamicMesh3& MeshToUpdate)
{
MeshToUpdate = MoveTemp(SolidMesh);
});
}
void ADynamicMeshBaseActor::SimplifyMeshToTriCount(int32 TargetTriangleCount)
{
TargetTriangleCount = FMath::Max(1, TargetTriangleCount);
if (TargetTriangleCount >= SourceMesh.TriangleCount()) return;
// make compacted copy because it seems to change the results?
FDynamicMesh3 SimplifyMesh;
SimplifyMesh.CompactCopy(SourceMesh, false, false, false, false);
SimplifyMesh.EnableTriangleGroups(); // workaround for failing check()
FQEMSimplification Simplifier(&SimplifyMesh);
Simplifier.SimplifyToTriangleCount(TargetTriangleCount);
SimplifyMesh.EnableAttributes();
RecomputeNormals(SimplifyMesh);
EditMesh([&](FDynamicMesh3& MeshToUpdate)
{
MeshToUpdate.CompactCopy(SimplifyMesh);
});
}

View File

@ -0,0 +1,141 @@
#include "DynamicMeshOBJReader.h"
#include "DynamicMeshAttributeSet.h"
#include "tinyobj/tiny_obj_loader.h"
bool RTGUtils::ReadOBJMesh(
const FString& Path,
FDynamicMesh3& MeshOut,
bool bNormals,
bool bTexCoords,
bool bVertexColors,
bool bReverseOrientation)
{
std::string inputfile(TCHAR_TO_UTF8(*Path));
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string warn;
std::string err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, inputfile.c_str());
if (!warn.empty())
{
UE_LOG(LogTemp, Display, TEXT("%s"), warn.c_str());
}
if (!err.empty())
{
UE_LOG(LogTemp, Display, TEXT("%s"), err.c_str());
}
if (!ret)
{
return false;
}
// append vertices
for (size_t vi = 0; vi < attrib.vertices.size() / 3; ++vi)
{
tinyobj::real_t vx = attrib.vertices[3 * vi + 0];
tinyobj::real_t vy = attrib.vertices[3 * vi + 1];
tinyobj::real_t vz = attrib.vertices[3 * vi + 2];
MeshOut.AppendVertex(FVector3d(vx, vy, vz));
}
if (bVertexColors)
{
MeshOut.EnableVertexColors(FVector3f::Zero());
for (size_t vi = 0; vi < attrib.vertices.size() / 3; ++vi)
{
tinyobj::real_t r = attrib.colors[3 * vi + 0];
tinyobj::real_t g = attrib.colors[3 * vi + 1];
tinyobj::real_t b = attrib.colors[3 * vi + 2];
MeshOut.SetVertexColor(vi, FVector3f((float)r, (float)g, (float)b));
}
}
if (bNormals || bTexCoords)
{
MeshOut.EnableAttributes();
}
FDynamicMeshNormalOverlay* Normals = (bNormals) ? MeshOut.Attributes()->PrimaryNormals() : nullptr;
FDynamicMeshUVOverlay* UVs = (bTexCoords) ? MeshOut.Attributes()->PrimaryUV() : nullptr;
if (Normals)
{
for (size_t ni = 0; ni < attrib.normals.size() / 3; ++ni)
{
tinyobj::real_t nx = attrib.normals[3 * ni + 0];
tinyobj::real_t ny = attrib.normals[3 * ni + 1];
tinyobj::real_t nz = attrib.normals[3 * ni + 2];
Normals->AppendElement(FVector3f((float)nx, (float)ny, (float)nz));
}
}
if (UVs)
{
for (size_t ti = 0; ti < attrib.texcoords.size() / 2; ++ti)
{
tinyobj::real_t tx = attrib.texcoords[2 * ti + 0];
tinyobj::real_t ty = attrib.texcoords[2 * ti + 1];
UVs->AppendElement(FVector2f((float)tx, (float)ty));
}
}
// append faces as triangles
for (size_t s = 0; s < shapes.size(); s++)
{
// Loop over shapes
size_t index_offset = 0;
for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++)
{
// Loop over faces(polygon)
int fv = shapes[s].mesh.num_face_vertices[f];
TArray<FIndex3i> Triangles;
for (size_t v = 1; v < fv - 1; v++)
{
Triangles.Add(FIndex3i(0, v, v + 1));
}
int32 NumTris = Triangles.Num();
for (int32 ti = 0; ti < NumTris; ++ti)
{
FIndex3i TriVerts = Triangles[ti];
tinyobj::index_t idx0 = shapes[s].mesh.indices[index_offset + TriVerts.A];
tinyobj::index_t idx1 = shapes[s].mesh.indices[index_offset + TriVerts.B];
tinyobj::index_t idx2 = shapes[s].mesh.indices[index_offset + TriVerts.C];
int32 tid = MeshOut.AppendTriangle(idx0.vertex_index, idx1.vertex_index, idx2.vertex_index);
if (Normals && Normals->IsElement(idx0.normal_index) && Normals->IsElement(idx1.normal_index) && Normals->IsElement(idx2.normal_index))
{
Normals->SetTriangle(tid, FIndex3i(idx0.normal_index, idx1.normal_index, idx2.normal_index));
}
if (UVs && UVs->IsElement(idx0.texcoord_index) && UVs->IsElement(idx1.texcoord_index) && UVs->IsElement(idx2.texcoord_index))
{
UVs->SetTriangle(tid, FIndex3i(idx0.texcoord_index, idx1.texcoord_index, idx2.texcoord_index));
}
}
index_offset += fv;
// per-face material
//shapes[s].mesh.material_ids[f];
}
}
if (bReverseOrientation)
{
MeshOut.ReverseOrientation();
}
return true;
}

View File

@ -0,0 +1,191 @@
#include "DynamicMeshOBJWriter.h"
#include "DynamicMeshAttributeSet.h"
#include "DynamicMeshEditor.h"
#include <fstream>
class FDynamicMeshOBJWriter
{
public:
std::ofstream FileOut;
TFunction<bool(const FString&)> OpenFile = [this](const FString& Path) { FileOut.open(*Path, std::ofstream::out | std::ofstream::trunc); return !!FileOut; };
TFunction<void()> CloseFile = [this]() { FileOut.close(); };
TFunction<void(const TCHAR*)> WriteLine = [this](const TCHAR* Line) { FileOut << TCHAR_TO_ANSI(Line) << std::endl; };
bool Write(const char* OutputPath, const FDynamicMesh3& Mesh)
{
if (!OpenFile(OutputPath))
{
return false;
}
int32 NumVertices = Mesh.VertexCount();
for (int32 vi = 0; vi < NumVertices; ++vi)
{
FVector3d Pos = Mesh.GetVertex(vi);
WriteLine(*FString::Printf(TEXT("v %f %f %f"), Pos.X, Pos.Y, Pos.Z));
}
int32 NumUVs = 0;
const FDynamicMeshUVOverlay* UVs = nullptr;
if (Mesh.Attributes() && Mesh.Attributes()->PrimaryUV())
{
UVs = Mesh.Attributes()->PrimaryUV();
NumUVs = UVs->ElementCount();
for (int32 ui = 0; ui < NumUVs; ++ui)
{
FVector2f UV = UVs->GetElement(ui);
WriteLine(*FString::Printf(TEXT("vt %f %f"), UV.X, UV.Y));
}
}
int32 NumNormals = 0;
const FDynamicMeshNormalOverlay* Normals = nullptr;
if (Mesh.Attributes() && Mesh.Attributes()->PrimaryNormals())
{
Normals = Mesh.Attributes()->PrimaryNormals();
NumNormals = Normals->ElementCount();
for (int32 ni = 0; ni < NumNormals; ++ni)
{
FVector3f Normal = Normals->GetElement(ni);
WriteLine(*FString::Printf(TEXT("vn %f %f %f"), Normal.X, Normal.Y, Normal.Z));
}
}
struct FMeshTri
{
int32 Index;
int32 Group;
};
TSet<int32> AllGroupIDs;
TArray<FMeshTri> Triangles;
int32 NumTriangles = Mesh.TriangleCount();
for (int32 ti = 0; ti < NumTriangles; ++ti)
{
if (Mesh.IsTriangle(ti))
{
int32 GroupID = Mesh.GetTriangleGroup(ti);
AllGroupIDs.Add(GroupID);
Triangles.Add({ ti, GroupID });
}
}
bool bHaveGroups = AllGroupIDs.Num() > 1;
Triangles.StableSort([](const FMeshTri& Tri0, const FMeshTri& Tri1)
{
return Tri0.Group < Tri1.Group;
});
int32 CurGroupID = -99999;
for (FMeshTri MeshTri : Triangles)
{
if (bHaveGroups && MeshTri.Group != CurGroupID)
{
WriteLine(*FString::Printf(TEXT("g %d"), MeshTri.Group));
CurGroupID = MeshTri.Group;
}
int32 ti = MeshTri.Index;
FIndex3i TriVertices = Mesh.GetTriangle(ti);
FIndex3i TriUVs = (NumUVs > 0) ? UVs->GetTriangle(ti) : FIndex3i::Invalid();
FIndex3i TriNormals = (NumNormals > 0) ? Normals->GetTriangle(ti) : FIndex3i::Invalid();
bool bHaveUV = (NumUVs != 0) && UVs->IsSetTriangle(ti);
bool bHaveNormal = (NumNormals != 0) && Normals->IsSetTriangle(ti);
if (bHaveUV && bHaveNormal)
{
WriteLine(*FString::Printf(TEXT("f %d/%d/%d %d/%d/%d %d/%d/%d"),
TriVertices.A + 1, TriUVs.A + 1, TriNormals.A + 1,
TriVertices.B + 1, TriUVs.B + 1, TriNormals.B + 1,
TriVertices.C + 1, TriUVs.C + 1, TriNormals.C + 1));
}
else if (bHaveUV)
{
WriteLine(*FString::Printf(TEXT("f %d/%d %d/%d %d/%d"),
TriVertices.A + 1, TriUVs.A + 1, TriVertices.B + 1, TriUVs.B + 1, TriVertices.C + 1, TriUVs.C + 1));
}
else if (bHaveNormal)
{
WriteLine(*FString::Printf(TEXT("f %d//%d %d//%d %d//%d"),
TriVertices.A + 1, TriNormals.A + 1, TriVertices.B + 1, TriNormals.B + 1, TriVertices.C + 1, TriNormals.C + 1));
}
else
{
WriteLine(*FString::Printf(TEXT("f %d %d %d"), TriVertices.A + 1, TriVertices.B + 1, TriVertices.C + 1));
}
}
CloseFile();
return true;
}
};
bool RTGUtils::WriteOBJMesh(
const FString& OutputPath,
const FDynamicMesh3& Mesh,
bool bReverseOrientation)
{
const FDynamicMesh3* WriteMesh = &Mesh;
FDynamicMesh3 CopyMesh;
if (bReverseOrientation)
{
CopyMesh = Mesh;
CopyMesh.ReverseOrientation();
WriteMesh = &CopyMesh;
}
FDynamicMeshOBJWriter Writer;
std::string OutputFilePath(TCHAR_TO_UTF8(*OutputPath));
return Writer.Write(OutputFilePath.c_str(), *WriteMesh);
}
bool RTGUtils::WriteOBJMeshes(
const FString& OutputPath,
const TArray<FDynamicMesh3>& Meshes,
bool bReverseOrientation)
{
FDynamicMesh3 CombinedMesh;
FDynamicMeshEditor Editor(&CombinedMesh);
for (const FDynamicMesh3& Mesh : Meshes)
{
if (Mesh.HasTriangleGroups())
{
CombinedMesh.EnableTriangleGroups();
}
if (Mesh.HasAttributes())
{
CombinedMesh.EnableAttributes();
}
FMeshIndexMappings Mappings;
Editor.AppendMesh(&Mesh, Mappings);
}
if (bReverseOrientation)
{
CombinedMesh.ReverseOrientation();
}
FDynamicMeshOBJWriter Writer;
std::string OutputFilePath(TCHAR_TO_UTF8(*OutputPath));
return Writer.Write(OutputFilePath.c_str(), CombinedMesh);
}

View File

@ -0,0 +1,55 @@
#include "DynamicPMCActor.h"
#include "MeshComponentRuntimeUtils.h"
#include "DynamicMesh3.h"
// Sets default values
ADynamicPMCActor::ADynamicPMCActor()
{
MeshComponent = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("Mesh"), false);
SetRootComponent(MeshComponent);
}
// Called when the game starts or when spawned
void ADynamicPMCActor::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ADynamicPMCActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ADynamicPMCActor::OnMeshEditedInternal()
{
UpdatePMCMesh();
Super::OnMeshEditedInternal();
}
void ADynamicPMCActor::UpdatePMCMesh()
{
if (MeshComponent)
{
bool bUseFaceNormals = (this->NormalsMode == EDynamicMeshActorNormalsMode::FaceNormals);
bool bUseUV0 = true;
bool bUseVertexColors = false;
bool bGenerateSectionCollision = false;
if (this->CollisionMode == EDynamicMeshActorCollisionMode::ComplexAsSimple
|| this->CollisionMode == EDynamicMeshActorCollisionMode::ComplexAsSimpleAsync)
{
bGenerateSectionCollision = true;
MeshComponent->bUseAsyncCooking = (this->CollisionMode == EDynamicMeshActorCollisionMode::ComplexAsSimpleAsync);
MeshComponent->bUseComplexAsSimpleCollision = true;
}
RTGUtils::UpdatePMCFromDynamicMesh_SplitTriangles(MeshComponent, &SourceMesh, bUseFaceNormals, bUseUV0, bUseVertexColors, bGenerateSectionCollision);
// update material on new section
UMaterialInterface* UseMaterial = (this->Material != nullptr) ? this->Material : UMaterial::GetDefaultMaterial(MD_Surface);
MeshComponent->SetMaterial(0, UseMaterial);
}
}

View File

@ -0,0 +1,45 @@
#include "DynamicSDMCActor.h"
#include "DynamicMesh3.h"
#include "MeshUtilities2/Public/DistanceFieldAtlas2.h"
// Sets default values
ADynamicSDMCActor::ADynamicSDMCActor()
{
MeshComponent = CreateDefaultSubobject<USimpleDynamicMeshComponent>(TEXT("MeshComponent"), false);
SetRootComponent(MeshComponent);
}
// Called when the game starts or when spawned
void ADynamicSDMCActor::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ADynamicSDMCActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ADynamicSDMCActor::OnMeshEditedInternal()
{
UpdateSDMCMesh();
Super::OnMeshEditedInternal();
}
void ADynamicSDMCActor::UpdateSDMCMesh()
{
if (MeshComponent)
{
*(MeshComponent->GetMesh()) = SourceMesh;
MeshComponent->NotifyMeshUpdated();
// update material on new section
UMaterialInterface* UseMaterial = (this->Material != nullptr) ? this->Material : UMaterial::GetDefaultMaterial(MD_Surface);
MeshComponent->SetMaterial(0, UseMaterial);
}
}

View File

@ -0,0 +1,63 @@
#include "DynamicSMCActor.h"
#include "MeshComponentRuntimeUtils.h"
// Sets default values
ADynamicSMCActor::ADynamicSMCActor()
{
MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"), false);
SetRootComponent(MeshComponent);
StaticMesh = nullptr;
}
// Called when the game starts or when spawned
void ADynamicSMCActor::BeginPlay()
{
StaticMesh = nullptr;
Super::BeginPlay();
}
void ADynamicSMCActor::PostLoad()
{
StaticMesh = nullptr;
Super::PostLoad();
}
void ADynamicSMCActor::PostActorCreated()
{
StaticMesh = nullptr;
Super::PostActorCreated();
}
// Called every frame
void ADynamicSMCActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ADynamicSMCActor::OnMeshEditedInternal()
{
UpdateSMCMesh();
Super::OnMeshEditedInternal();
}
void ADynamicSMCActor::UpdateSMCMesh()
{
if (StaticMesh == nullptr)
{
StaticMesh = NewObject<UStaticMesh>();
MeshComponent->SetStaticMesh(StaticMesh);
// add one material slot
StaticMesh->GetStaticMaterials().Add(FStaticMaterial());
}
if (MeshComponent)
{
auto MTL = LoadObject<UMaterialInterface>(nullptr,TEXT("Material'/Game/baise.baise'"));
// update material on new section
UMaterialInterface* UseMaterial = (this->Material != nullptr) ? this->Material : MTL;
MeshComponent->SetMaterial(0, UseMaterial);
RTGUtils::UpdateStaticMeshFromDynamicMesh(StaticMesh, &SourceMesh);
}
}

View File

@ -0,0 +1,147 @@
#include "MeshComponentRuntimeUtils.h"
#include "DynamicMeshAttributeSet.h"
#include "MeshNormals.h"
#include "DynamicMeshToMeshDescription.h"
#include "StaticMeshAttributes.h"
#include "MeshUtilities2/Public/DistanceFieldAtlas2.h"
void RTGUtils::UpdateStaticMeshFromDynamicMesh(
UStaticMesh* StaticMesh,
const FDynamicMesh3* Mesh)
{
FMeshDescription MeshDescription;
FStaticMeshAttributes StaticMeshAttributes(MeshDescription);
StaticMeshAttributes.Register();
FDynamicMeshToMeshDescription Converter;
Converter.Convert(Mesh, MeshDescription);
// todo: vertex color support
//UStaticMesh* StaticMesh = NewObject<UStaticMesh>(Component);
//FName MaterialSlotName = StaticMesh->AddMaterial(MyMaterial);
// Build the static mesh render data, one FMeshDescription* per LOD.
TArray<const FMeshDescription*> MeshDescriptionPtrs;
MeshDescriptionPtrs.Emplace(&MeshDescription);
UStaticMesh::FBuildMeshDescriptionsParams Params;
Params.bFastBuild = true;
StaticMesh->BuildFromMeshDescriptions(MeshDescriptionPtrs,Params);
//在编辑器模式下,也使用自定义的距离场构建和MeshCard构建
BuildMeshDistanceField(StaticMesh);
}
void RTGUtils::UpdatePMCFromDynamicMesh_SplitTriangles(
UProceduralMeshComponent* Component,
const FDynamicMesh3* Mesh,
bool bUseFaceNormals,
bool bInitializeUV0,
bool bInitializePerVertexColors,
bool bCreateCollision)
{
Component->ClearAllMeshSections();
int32 NumTriangles = Mesh->TriangleCount();
int32 NumVertices = NumTriangles * 3;
TArray<FVector> Vertices, Normals;
Vertices.SetNumUninitialized(NumVertices);
Normals.SetNumUninitialized(NumVertices);
FMeshNormals PerVertexNormals(Mesh);
bool bUsePerVertexNormals = false;
const FDynamicMeshNormalOverlay* NormalOverlay = nullptr;
if (Mesh->HasAttributes() == false && bUseFaceNormals == false)
{
PerVertexNormals.ComputeVertexNormals();
bUsePerVertexNormals = true;
}
else if (Mesh->HasAttributes())
{
NormalOverlay = Mesh->Attributes()->PrimaryNormals();
}
const FDynamicMeshUVOverlay* UVOverlay = (Mesh->HasAttributes()) ? Mesh->Attributes()->PrimaryUV() : nullptr;
TArray<FVector2D> UV0;
if (UVOverlay && bInitializeUV0)
{
UV0.SetNum(NumVertices);
}
TArray<FLinearColor> VtxColors;
bool bUsePerVertexColors = false;
if (bInitializePerVertexColors && Mesh->HasVertexColors())
{
VtxColors.SetNum(NumVertices);
bUsePerVertexColors = true;
}
TArray<FProcMeshTangent> Tangents; // not supporting this for now
TArray<int32> Triangles;
Triangles.SetNumUninitialized(NumTriangles * 3);
FVector3d Position[3];
FVector3f Normal[3];
FVector2f UV[3];
int32 BufferIndex = 0;
for (int32 tid : Mesh->TriangleIndicesItr())
{
int32 k = 3 * (BufferIndex++);
FIndex3i TriVerts = Mesh->GetTriangle(tid);
Mesh->GetTriVertices(tid, Position[0], Position[1], Position[2]);
Vertices[k] = (FVector)Position[0];
Vertices[k + 1] = (FVector)Position[1];
Vertices[k + 2] = (FVector)Position[2];
if (bUsePerVertexNormals)
{
Normals[k] = (FVector)PerVertexNormals[TriVerts.A];
Normals[k + 1] = (FVector)PerVertexNormals[TriVerts.B];
Normals[k + 2] = (FVector)PerVertexNormals[TriVerts.C];
}
else if (NormalOverlay != nullptr && bUseFaceNormals == false)
{
NormalOverlay->GetTriElements(tid, Normal[0], Normal[1], Normal[2]);
Normals[k] = (FVector)Normal[0];
Normals[k + 1] = (FVector)Normal[1];
Normals[k + 2] = (FVector)Normal[2];
}
else
{
FVector3d TriNormal = Mesh->GetTriNormal(tid);
Normals[k] = (FVector)TriNormal;
Normals[k + 1] = (FVector)TriNormal;
Normals[k + 2] = (FVector)TriNormal;
}
if (UVOverlay != nullptr && UVOverlay->IsSetTriangle(tid))
{
UVOverlay->GetTriElements(tid, UV[0], UV[1], UV[2]);
UV0[k] = (FVector2D)UV[0];
UV0[k + 1] = (FVector2D)UV[1];
UV0[k + 2] = (FVector2D)UV[2];
}
if (bUsePerVertexColors)
{
VtxColors[k] = (FLinearColor)Mesh->GetVertexColor(TriVerts.A);
VtxColors[k + 1] = (FLinearColor)Mesh->GetVertexColor(TriVerts.B);
VtxColors[k + 2] = (FLinearColor)Mesh->GetVertexColor(TriVerts.C);
}
Triangles[k] = k;
Triangles[k + 1] = k + 1;
Triangles[k + 2] = k + 2;
}
Component->CreateMeshSection_LinearColor(0, Vertices, Triangles, Normals, UV0, VtxColors, Tangents, bCreateCollision);
}

View File

@ -0,0 +1,20 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "RuntimeGeometryUtilsModule.h"
#define LOCTEXT_NAMESPACE "FRuntimeGeometryUtilsModule"
void FRuntimeGeometryUtilsModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}
void FRuntimeGeometryUtilsModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FRuntimeGeometryUtilsModule, RuntimeGeometryUtils)

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012-2019 Syoyo Fujita and many contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,334 @@
# tinyobjloader
[![Build Status](https://travis-ci.org/tinyobjloader/tinyobjloader.svg?branch=master)](https://travis-ci.org/tinyobjloader/tinyobjloader)
[![AZ Build Status](https://dev.azure.com/tinyobjloader/tinyobjloader/_apis/build/status/tinyobjloader.tinyobjloader?branchName=master)](https://dev.azure.com/tinyobjloader/tinyobjloader/_build/latest?definitionId=1&branchName=master)
[![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/m6wfkvket7gth8wn/branch/master?svg=true)](https://ci.appveyor.com/project/syoyo/tinyobjloader-6e4qf/branch/master)
[![Coverage Status](https://coveralls.io/repos/github/syoyo/tinyobjloader/badge.svg?branch=master)](https://coveralls.io/github/syoyo/tinyobjloader?branch=master)
[![AUR version](https://img.shields.io/aur/version/tinyobjloader?logo=arch-linux)](https://aur.archlinux.org/packages/tinyobjloader)
[![Download](https://api.bintray.com/packages/conan/conan-center/tinyobjloader%3A_/images/download.svg)](https://bintray.com/conan/conan-center/tinyobjloader%3A_/_latestVersion) (not recommended)
Tiny but powerful single file wavefront obj loader written in C++03. No dependency except for C++ STL. It can parse over 10M polygons with moderate memory and time.
`tinyobjloader` is good for embedding .obj loader to your (global illumination) renderer ;-)
If you are looking for C89 version, please see https://github.com/syoyo/tinyobjloader-c .
Notice!
-------
We have released new version v1.0.0 on 20 Aug, 2016.
Old version is available as `v0.9.x` branch https://github.com/syoyo/tinyobjloader/tree/v0.9.x
## What's new
* 19 Feb, 2020 : The repository has been moved to https://github.com/tinyobjloader/tinyobjloader !
* 18 May, 2019 : Python binding!(See `python` folder. Also see https://pypi.org/project/tinyobjloader/)
* 14 Apr, 2019 : Bump version v2.0.0 rc0. New C++ API and python bindings!(1.x API still exists for backward compatibility)
* 20 Aug, 2016 : Bump version v1.0.0. New data structure and API!
## Requirements
* C++03 compiler
### Old version
Previous old version is available in `v0.9.x` branch.
## Example
![Rungholt](images/rungholt.jpg)
tinyobjloader can successfully load 6M triangles Rungholt scene.
http://casual-effects.com/data/index.html
![](images/sanmugel.png)
* [examples/viewer/](examples/viewer) OpenGL .obj viewer
* [examples/callback_api/](examples/callback_api/) Callback API example
* [examples/voxelize/](examples/voxelize/) Voxelizer example
## Use case
TinyObjLoader is successfully used in ...
### New version(v1.0.x)
* Double precision support through `TINYOBJLOADER_USE_DOUBLE` thanks to noma
* Loading models in Vulkan Tutorial https://vulkan-tutorial.com/Loading_models
* .obj viewer with Metal https://github.com/middlefeng/NuoModelViewer/tree/master
* Vulkan Cookbook https://github.com/PacktPublishing/Vulkan-Cookbook
* cudabox: CUDA Solid Voxelizer Engine https://github.com/gaspardzoss/cudavox
* Drake: A planning, control, and analysis toolbox for nonlinear dynamical systems https://github.com/RobotLocomotion/drake
* VFPR - a Vulkan Forward Plus Renderer : https://github.com/WindyDarian/Vulkan-Forward-Plus-Renderer
* glslViewer: https://github.com/patriciogonzalezvivo/glslViewer
* Lighthouse2: https://github.com/jbikker/lighthouse2
* rayrender(an open source R package for raytracing scenes in created in R): https://github.com/tylermorganwall/rayrender
* liblava - A modern C++ and easy-to-use framework for the Vulkan API. [MIT]: https://github.com/liblava/liblava
* rtxON - Simple Vulkan raytracing tutorials https://github.com/iOrange/rtxON
* metal-ray-tracer - Writing ray-tracer using Metal Performance Shaders https://github.com/sergeyreznik/metal-ray-tracer https://sergeyreznik.github.io/metal-ray-tracer/index.html
* Your project here! (Letting us know via github issue is welcome!)
### Old version(v0.9.x)
* bullet3 https://github.com/erwincoumans/bullet3
* pbrt-v2 https://github.com/mmp/pbrt-v2
* OpenGL game engine development http://swarminglogic.com/jotting/2013_10_gamedev01
* mallie https://lighttransport.github.io/mallie
* IBLBaker (Image Based Lighting Baker). http://www.derkreature.com/iblbaker/
* Stanford CS148 http://web.stanford.edu/class/cs148/assignments/assignment3.pdf
* Awesome Bump http://awesomebump.besaba.com/about/
* sdlgl3-wavefront OpenGL .obj viewer https://github.com/chrisliebert/sdlgl3-wavefront
* pbrt-v3 https://github.com/mmp/pbrt-v3
* cocos2d-x https://github.com/cocos2d/cocos2d-x/
* Android Vulkan demo https://github.com/SaschaWillems/Vulkan
* voxelizer https://github.com/karimnaaji/voxelizer
* Probulator https://github.com/kayru/Probulator
* OptiX Prime baking https://github.com/nvpro-samples/optix_prime_baking
* FireRays SDK https://github.com/GPUOpen-LibrariesAndSDKs/FireRays_SDK
* parg, tiny C library of various graphics utilities and GL demos https://github.com/prideout/parg
* Opengl unit of ChronoEngine https://github.com/projectchrono/chrono-opengl
* Point Based Global Illumination on modern GPU https://pbgi.wordpress.com/code-source/
* Fast OBJ file importing and parsing in CUDA http://researchonline.jcu.edu.au/42515/1/2015.CVM.OBJCUDA.pdf
* Sorted Shading for Uni-Directional Pathtracing by Joshua Bainbridge https://nccastaff.bournemouth.ac.uk/jmacey/MastersProjects/MSc15/02Josh/joshua_bainbridge_thesis.pdf
* GeeXLab http://www.geeks3d.com/hacklab/20160531/geexlab-0-12-0-0-released-for-windows/
## Features
* Group(parse multiple group name)
* Vertex
* Vertex color(as an extension: https://blender.stackexchange.com/questions/31997/how-can-i-get-vertex-painted-obj-files-to-import-into-blender)
* Texcoord
* Normal
* Material
* Unknown material attributes are returned as key-value(value is string) map.
* Crease tag('t'). This is OpenSubdiv specific(not in wavefront .obj specification)
* PBR material extension for .MTL. Its proposed here: http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr
* Callback API for custom loading.
* Double precision support(for HPC application).
* Smoothing group
* Python binding : See `python` folder.
* Precompiled binary(manylinux1-x86_64 only) is hosted at pypi https://pypi.org/project/tinyobjloader/)
### Primitives
* [x] face(`f`)
* [x] lines(`l`)
* [ ] points(`p`)
* [ ] curve
* [ ] 2D curve
* [ ] surface.
* [ ] Free form curve/surfaces
## TODO
* [ ] Fix obj_sticker example.
* [ ] More unit test codes.
* [x] Texture options
## License
TinyObjLoader is licensed under MIT license.
### Third party licenses.
* pybind11 : BSD-style license.
## Usage
### Installation
One option is to simply copy the header file into your project and to make sure that `TINYOBJLOADER_IMPLEMENTATION` is defined exactly once.
Tinyobjlaoder is also available as a [conan package](https://bintray.com/conan/conan-center/tinyobjloader%3A_/_latestVersion). Conan integrates with many build systems and lets you avoid manual dependency installation. Their [documentation](https://docs.conan.io/en/latest/getting_started.html) is a great starting point.
### Building tinyobjloader - Using vcpkg
You can download and install tinyobjloader using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install tinyobjloader
The tinyobjloader port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
### Data format
`attrib_t` contains single and linear array of vertex data(position, normal and texcoord).
```
attrib_t::vertices => 3 floats per vertex
v[0] v[1] v[2] v[3] v[n-1]
+-----------+-----------+-----------+-----------+ +-----------+
| x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z |
+-----------+-----------+-----------+-----------+ +-----------+
attrib_t::normals => 3 floats per vertex
n[0] n[1] n[2] n[3] n[n-1]
+-----------+-----------+-----------+-----------+ +-----------+
| x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z |
+-----------+-----------+-----------+-----------+ +-----------+
attrib_t::texcoords => 2 floats per vertex
t[0] t[1] t[2] t[3] t[n-1]
+-----------+-----------+-----------+-----------+ +-----------+
| u | v | u | v | u | v | u | v | .... | u | v |
+-----------+-----------+-----------+-----------+ +-----------+
attrib_t::colors => 3 floats per vertex(vertex color. optional)
c[0] c[1] c[2] c[3] c[n-1]
+-----------+-----------+-----------+-----------+ +-----------+
| x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z |
+-----------+-----------+-----------+-----------+ +-----------+
```
Each `shape_t::mesh_t` does not contain vertex data but contains array index to `attrib_t`.
See `loader_example.cc` for more details.
```
mesh_t::indices => array of vertex indices.
+----+----+----+----+----+----+----+----+----+----+ +--------+
| i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7 | i8 | i9 | ... | i(n-1) |
+----+----+----+----+----+----+----+----+----+----+ +--------+
Each index has an array index to attrib_t::vertices, attrib_t::normals and attrib_t::texcoords.
mesh_t::num_face_vertices => array of the number of vertices per face(e.g. 3 = triangle, 4 = quad , 5 or more = N-gons).
+---+---+---+ +---+
| 3 | 4 | 3 | ...... | 3 |
+---+---+---+ +---+
| | | |
| | | +-----------------------------------------+
| | | |
| | +------------------------------+ |
| | | |
| +------------------+ | |
| | | |
|/ |/ |/ |/
mesh_t::indices
| face[0] | face[1] | face[2] | | face[n-1] |
+----+----+----+----+----+----+----+----+----+----+ +--------+--------+--------+
| i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7 | i8 | i9 | ... | i(n-3) | i(n-2) | i(n-1) |
+----+----+----+----+----+----+----+----+----+----+ +--------+--------+--------+
```
Note that when `triangulate` flag is true in `tinyobj::LoadObj()` argument, `num_face_vertices` are all filled with 3(triangle).
### float data type
TinyObjLoader now use `real_t` for floating point data type.
Default is `float(32bit)`.
You can enable `double(64bit)` precision by using `TINYOBJLOADER_USE_DOUBLE` define.
#### Example code
```c++
#define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc
#include "tiny_obj_loader.h"
std::string inputfile = "cornell_box.obj";
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string warn;
std::string err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, inputfile.c_str());
if (!warn.empty()) {
std::cout << warn << std::endl;
}
if (!err.empty()) {
std::cerr << err << std::endl;
}
if (!ret) {
exit(1);
}
// Loop over shapes
for (size_t s = 0; s < shapes.size(); s++) {
// Loop over faces(polygon)
size_t index_offset = 0;
for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) {
int fv = shapes[s].mesh.num_face_vertices[f];
// Loop over vertices in the face.
for (size_t v = 0; v < fv; v++) {
// access to vertex
tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
tinyobj::real_t vx = attrib.vertices[3*idx.vertex_index+0];
tinyobj::real_t vy = attrib.vertices[3*idx.vertex_index+1];
tinyobj::real_t vz = attrib.vertices[3*idx.vertex_index+2];
tinyobj::real_t nx = attrib.normals[3*idx.normal_index+0];
tinyobj::real_t ny = attrib.normals[3*idx.normal_index+1];
tinyobj::real_t nz = attrib.normals[3*idx.normal_index+2];
tinyobj::real_t tx = attrib.texcoords[2*idx.texcoord_index+0];
tinyobj::real_t ty = attrib.texcoords[2*idx.texcoord_index+1];
// Optional: vertex colors
// tinyobj::real_t red = attrib.colors[3*idx.vertex_index+0];
// tinyobj::real_t green = attrib.colors[3*idx.vertex_index+1];
// tinyobj::real_t blue = attrib.colors[3*idx.vertex_index+2];
}
index_offset += fv;
// per-face material
shapes[s].mesh.material_ids[f];
}
}
```
## Optimized loader
Optimized multi-threaded .obj loader is available at `experimental/` directory.
If you want absolute performance to load .obj data, this optimized loader will fit your purpose.
Note that the optimized loader uses C++11 thread and it does less error checks but may work most .obj data.
Here is some benchmark result. Time are measured on MacBook 12(Early 2016, Core m5 1.2GHz).
* Rungholt scene(6M triangles)
* old version(v0.9.x): 15500 msecs.
* baseline(v1.0.x): 6800 msecs(2.3x faster than old version)
* optimised: 1500 msecs(10x faster than old version, 4.5x faster than baseline)
## Python binding
### CI + PyPI upload
cibuildwheels + twine upload for each git tagging event is handled in Azure Pipeline.
#### How to bump version(For developer)
* Bump version in CMakeLists.txt
* Update version in `python/setup.py`
* Commit with tag name starging with `v`(e.g. `v2.1.0`)
* `git push --tags`
* cibuildwheels + pypi upload(through twine) will be automatically triggered in Azure Pipeline.
## Tests
Unit tests are provided in `tests` directory. See `tests/README.md` for details.

View File

@ -0,0 +1,2 @@
#define TINYOBJLOADER_IMPLEMENTATION
#include "tiny_obj_loader.h"

View File

@ -0,0 +1,373 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "DynamicMesh3.h"
#include "DynamicMeshAABBTree3.h"
#include "Spatial/FastWinding.h"
#include "DynamicMeshBaseActor.generated.h"
/**
* Type of Normals computation used by ADynamicMeshBaseActor
*/
UENUM()
enum class EDynamicMeshActorNormalsMode : uint8
{
SplitNormals = 0,
PerVertexNormals = 1,
FaceNormals = 2
};
/**
* Source of mesh used to initialize ADynamicMeshBaseActor
*/
UENUM()
enum class EDynamicMeshActorSourceType : uint8
{
Primitive,
ImportedMesh
};
/**
* Geometric primitive types supported of by ADynamicMeshBaseActor
*/
UENUM()
enum class EDynamicMeshActorPrimitiveType : uint8
{
Sphere,
Box
};
/**
* Boolean operation types supported of by ADynamicMeshBaseActor
*/
UENUM(BlueprintType)
enum class EDynamicMeshActorBooleanOperation : uint8
{
Union,
Subtraction,
Intersection
};
UENUM(BlueprintType)
enum class EDynamicMeshActorCollisionMode : uint8
{
NoCollision,
ComplexAsSimple,
ComplexAsSimpleAsync
};
/**
* ADynamicMeshBaseActor is a base class for Actors that support being
* rebuilt in-game after mesh editing operations. The base Actor itself
* does not have any Components, it should be used via one of the
* DynamicPMCActor, DynamicSMCActor, or DynamicSDMCActor subclasses.
*
* ADynamicMeshBaseActor provides a FDynamicMesh3 "Source Mesh", which can
* be modified via lambdas passed to the EditMesh() function, which will
* then cause necessary updates to happen to the implementing Components.
* An AABBTree and FastWindingTree can optionally be enabled with the
* bEnableSpatialQueries and bEnableInsideQueries flags.
*
* When Spatial queries are enabled, a set of UFunctions DistanceToPoint(),
* NearestPoint(), ContainsPoint(), and IntersectRay() are available via Blueprints
* on the relevant Actor. These functions *do not* depend on the UE4 Physics
* system to work.
*
* A small set of mesh modification UFunctions are also available via Blueprints,
* including BooleanWithMesh(), SolidifyMesh(), SimplifyMeshToTriCount(), and
* CopyFromMesh().
*
* Meshes can be read from OBJ files either using the ImportedMesh type for
* the SourceType property, or by calling the ImportMesh() UFunction from a Blueprint.
* Note that calling this in a Construction Script will be problematic in the Editor
* as the OBJ will be re-read any time the Actor is modified (including translated/rotated).
*
* Any Material set on the subclass Components will be overriden by the Material property.
*
*
*/
UCLASS(Abstract)
class RUNTIMEGEOMETRYUTILS_API ADynamicMeshBaseActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ADynamicMeshBaseActor();
public:
/** Type of mesh used to initialize this Actor - either a generated mesh Primitive or an Imported OBJ file */
UPROPERTY(EditAnywhere, Category = MeshOptions)
EDynamicMeshActorSourceType SourceType = EDynamicMeshActorSourceType::Primitive;
/** Type of normals computed for the Mesh */
UPROPERTY(EditAnywhere, Category = MeshOptions)
EDynamicMeshActorNormalsMode NormalsMode = EDynamicMeshActorNormalsMode::SplitNormals;
/** Material assigned to child Components created by subclasses */
UPROPERTY(EditAnywhere, Category = MaterialOptions)
UMaterialInterface* Material;
/** If true, mesh will be regenerated or re-imported on tick. Can be useful for prototyping procedural animation, but not the most efficient way to do it */
UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
bool bRegenerateOnTick = false;
//
// Parameters for SourceType = Imported
//
/** Path to OBJ file read to initialize mesh in SourceType=Imported mode */
UPROPERTY(EditAnywhere, Category = ImportOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::ImportedMesh", EditConditionHides))
FString ImportPath;
/** Whether the imported mesh should have it's triangles reversed (commonly required for meshes authored in DCC tools) */
UPROPERTY(EditAnywhere, Category = ImportOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::ImportedMesh", EditConditionHides))
bool bReverseOrientation = true;
/** If true the imported mesh will be recentered around the origin */
UPROPERTY(EditAnywhere, Category = ImportOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::ImportedMesh", EditConditionHides))
bool bCenterPivot = true;
/** Uniform scaling applied to the imported mesh (baked into the mesh vertices, not the actor Transform) */
UPROPERTY(EditAnywhere, Category = ImportOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::ImportedMesh", EditConditionHides))
float ImportScale = 1.0;
//
// Parameters for SourceType = Primitive
//
/** Type of generated mesh primitive */
UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
EDynamicMeshActorPrimitiveType PrimitiveType = EDynamicMeshActorPrimitiveType::Box;
/** Triangle density of generated primitive */
UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (UIMin = 0, UIMax = 50, EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
int TessellationLevel = 8;
/** Radius of generated sphere / Width of generated box */
UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (UIMin = 0, EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
float MinimumRadius = 50;
/** Multiplier on MinimumRadius used to define box depth (ie dimension on Z axis) */
UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (UIMin = 0.01, UIMax = 1.0, EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive && PrimitiveType == EDynamicMeshActorPrimitiveType::Box", EditConditionHides))
float BoxDepthRatio = 1.0;
/** A random value in range [-VariableRadius, VariableRadius] is added to MinimumRadius */
UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (UIMin = 0, EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
float VariableRadius = 0;
/** Speed of variation of VariableRadius, only has an effect when bRegenerateOnTick is true */
UPROPERTY(EditAnywhere, Category = PrimitiveOptions, meta = (UIMin = 0, EditCondition = "SourceType == EDynamicMeshActorSourceType::Primitive", EditConditionHides))
float PulseSpeed = 3.0;
//
// ADynamicMeshBaseActor API
//
public:
/**
* Call EditMesh() to safely modify the SourceMesh owned by this Actor.
* Your EditFunc will be called with the Current SourceMesh as argument,
* and you are expected to pass back the new/modified version.
* (If you are generating an entirely new mesh, MoveTemp can be used to do this without a copy)
*/
virtual void EditMesh(TFunctionRef<void(FDynamicMesh3&)> EditFunc);
/**
* Get a copy of the current SourceMesh stored in MeshOut
*/
virtual void GetMeshCopy(FDynamicMesh3& MeshOut);
/**
* Get a reference to the current SourceMesh
*/
virtual const FDynamicMesh3& GetMeshRef() const;
/**
* This delegate is broadcast whenever the internal SourceMesh is updated
*/
DECLARE_MULTICAST_DELEGATE_OneParam(FOnMeshModified, ADynamicMeshBaseActor*);
FOnMeshModified OnMeshModified;
protected:
/** The SourceMesh used to initialize the mesh Components in the various subclasses */
FDynamicMesh3 SourceMesh;
/** Accumulated time since Actor was created, this is used for the animated primitives when bRegenerateOnTick = true*/
double AccumulatedTime = 0;
/** Called whenever the initial Source mesh needs to be regenerated / re-imported. Calls EditMesh() to do so. */
virtual void OnMeshGenerationSettingsModified();
/** Called to generate or import a new source mesh. Override this to provide your own generated mesh. */
virtual void RegenerateSourceMesh(FDynamicMesh3& MeshOut);
/** Call this on a Mesh to compute normals according to the NormalsMode setting */
virtual void RecomputeNormals(FDynamicMesh3& MeshOut);
//
// Support for AABBTree / Spatial Queries
//
public:
UPROPERTY(EditAnywhere, Category = SpatialQueryOptions)
bool bEnableSpatialQueries = false;
UPROPERTY(EditAnywhere, Category = SpatialQueryOptions)
bool bEnableInsideQueries = false;
protected:
// This AABBTree is updated each time SourceMesh is modified if bEnableSpatialQueries=true or bEnableInsideQueries=true
FDynamicMeshAABBTree3 MeshAABBTree;
// This FastWindingTree is updated each time SourceMesh is modified if bEnableInsideQueries=true
TUniquePtr<TFastWindingTree<FDynamicMesh3>> FastWinding;
//
// Support for Runtime-Generated Collision
//
public:
UPROPERTY(EditAnywhere, Category = RuntimeCollisionOptions)
EDynamicMeshActorCollisionMode CollisionMode = EDynamicMeshActorCollisionMode::NoCollision;
//
// ADynamicMeshBaseActor API that subclasses must implement.
//
protected:
/**
* Called when the SourceMesh has been modified. Subclasses override this function to
* update their respective Component with the new SourceMesh.
*/
virtual void OnMeshEditedInternal();
//
// Standard UE4 Actor Callbacks. If you need to override these functions,
// make sure to call (eg) Super::Tick() or you will break the mesh updating functionality.
//
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual void PostLoad() override;
virtual void PostActorCreated() override;
#if WITH_EDITOR
// called when property is modified. This will call OnMeshGenerationSettingsModified() to update the mesh
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
//
// Blueprint API
//
public:
/**
* Update SourceMesh by reading external mesh file at Path. Optionally flip orientation and recompute normals.
* Note: Path may be relative to Content folder, otherwise it must be an absolute path.
* @return false if mesh read failed
*/
UFUNCTION(BlueprintCallable)
bool ImportMesh(FString Path, bool bFlipOrientation, bool bRecomputeNormals);
/** Copy the SourceMesh of OtherMesh into our SourceMesh, and optionally recompute normals */
UFUNCTION(BlueprintCallable)
void CopyFromMesh(ADynamicMeshBaseActor* OtherMesh, bool bRecomputeNormals);
//
// Mesh Spatial Queries API
//
public:
/**
* Find NearestMeshWorldPoint on SourceMesh to WorldPoint, as well as NearestTriangle ID and barycentric coordinates of NearestMeshWorldPoint in triangle
* @return distance to point
*/
UFUNCTION(BlueprintCallable)
float DistanceToPoint(FVector WorldPoint, FVector& NearestMeshWorldPoint, int& NearestTriangle, FVector& TriBaryCoords);
/**
* @return nearest world-space point on SourceMesh to WorldPoint
*/
UFUNCTION(BlueprintCallable)
FVector NearestPoint(FVector WorldPoint);
/**
* @return true if mesh contains WorldPoint, which is defined as the mesh winding number being >= WindingThreshold
*/
UFUNCTION(BlueprintCallable)
bool ContainsPoint(FVector WorldPoint, float WindingThreshold = 0.5);
/**
* Calculate intersection of given 3D World-Space ray defined by (RayOrigin,RayDirection) with the SourceMesh.
* If hit, returns WorldHitPoint position, distance along ray in HitDistance, NearestTriangle ID, and barycentric coordinates of hit point in triangle
* Pass MaxDistance > 0 to limit the allowable ray-hit distance
* @return true if hit is found
*/
UFUNCTION(BlueprintCallable)
bool IntersectRay(FVector RayOrigin, FVector RayDirection, FVector& WorldHitPoint, float& HitDistance, int& NearestTriangle, FVector& TriBaryCoords, float MaxDistance = 0);
//
// Mesh Modification API
//
public:
/** Compute the specified a Boolean operation with OtherMesh (transformed to world space) and store in our SourceMesh */
UFUNCTION(BlueprintCallable)
void BooleanWithMesh(ADynamicMeshBaseActor* OtherMesh, EDynamicMeshActorBooleanOperation Operation);
/** Subtract OtherMesh from our SourceMesh */
UFUNCTION(BlueprintCallable)
void SubtractMesh(ADynamicMeshBaseActor* OtherMesh);
/** Union OtherMesh with our SourceMesh */
UFUNCTION(BlueprintCallable)
void UnionWithMesh(ADynamicMeshBaseActor* OtherMesh);
/** Intersect OtherMesh with our SourceMesh */
UFUNCTION(BlueprintCallable)
void IntersectWithMesh(ADynamicMeshBaseActor* OtherMesh);
/** Create a "solid" verison of SourceMesh by voxelizing with the fast winding number at the given grid resolution */
UFUNCTION(BlueprintCallable)
void SolidifyMesh(int VoxelResolution = 64, float WindingThreshold = 0.5);
/** Simplify current SourceMesh to the target triangle count */
UFUNCTION(BlueprintCallable)
void SimplifyMeshToTriCount(int32 TargetTriangleCount);
public:
/** @return number of triangles in current SourceMesh */
UFUNCTION(BlueprintCallable)
int GetTriangleCount();
};

View File

@ -0,0 +1,24 @@
#pragma once
#include "CoreMinimal.h"
#include "DynamicMesh3.h"
namespace RTGUtils
{
/**
* Read mesh in OBJ format from the given path into a FDynamicMesh3.
* @param bNormals should normals be imported into primary normal attribute overlay
* @param bTexCoords should texture coordinates be imported into primary UV attribute overlay
* @param bVertexColors should normals be imported into per-vertex colors
* @param bReverseOrientation if true, mesh orientation/normals are flipped. You probably want this for importing to UE4 from other apps.
* @param return false if read failed
*/
RUNTIMEGEOMETRYUTILS_API bool ReadOBJMesh(
const FString& Path,
FDynamicMesh3& MeshOut,
bool bNormals,
bool bTexCoords,
bool bVertexColors,
bool bReverseOrientation);
}

View File

@ -0,0 +1,30 @@
#pragma once
#include "CoreMinimal.h"
#include "DynamicMesh3.h"
namespace RTGUtils
{
/**
* Write mesh to the given output path in OBJ format.
* @param bReverseOrientation if true, mesh orientation/normals are flipped. You probably want this for exporting from UE4 to other apps.
* @param return false if write failed
*/
RUNTIMEGEOMETRYUTILS_API bool WriteOBJMesh(
const FString& OutputPath,
const FDynamicMesh3& Mesh,
bool bReverseOrientation);
/**
* Write set of meshes to the given output path in OBJ format.
* @param bReverseOrientation if true, mesh orientation/normals are flipped. You probably want this for exporting from UE4 to other apps.
* @param return false if write failed
*/
RUNTIMEGEOMETRYUTILS_API bool WriteOBJMeshes(
const FString& OutputPath,
const TArray<FDynamicMesh3>& Meshes,
bool bReverseOrientation);
}

View File

@ -0,0 +1,44 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ProceduralMeshComponent.h"
#include "DynamicMeshBaseActor.h"
#include "DynamicPMCActor.generated.h"
UCLASS()
class RUNTIMEGEOMETRYUTILS_API ADynamicPMCActor : public ADynamicMeshBaseActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ADynamicPMCActor();
public:
UPROPERTY(VisibleAnywhere)
UProceduralMeshComponent* MeshComponent = nullptr;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
protected:
/**
* ADynamicBaseActor API
*/
virtual void OnMeshEditedInternal() override;
protected:
virtual void UpdatePMCMesh();
};

View File

@ -0,0 +1,41 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SimpleDynamicMeshComponent.h"
#include "DynamicMeshBaseActor.h"
#include "DynamicSDMCActor.generated.h"
class FDynamicMesh3;
UCLASS()
class RUNTIMEGEOMETRYUTILS_API ADynamicSDMCActor : public ADynamicMeshBaseActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ADynamicSDMCActor();
public:
UPROPERTY(VisibleAnywhere)
USimpleDynamicMeshComponent* MeshComponent = nullptr;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
protected:
/**
* ADynamicBaseActor API
*/
virtual void OnMeshEditedInternal() override;
protected:
virtual void UpdateSDMCMesh();
};

View File

@ -0,0 +1,45 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Engine/StaticMesh.h"
#include "Components/StaticMeshComponent.h"
#include "DynamicMeshBaseActor.h"
#include "DynamicSMCActor.generated.h"
UCLASS()
class RUNTIMEGEOMETRYUTILS_API ADynamicSMCActor : public ADynamicMeshBaseActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ADynamicSMCActor();
public:
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* MeshComponent = nullptr;
UPROPERTY(Transient)
UStaticMesh* StaticMesh = nullptr;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual void PostLoad() override;
virtual void PostActorCreated() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
protected:
/**
* ADynamicBaseActor API
*/
virtual void OnMeshEditedInternal() override;
protected:
virtual void UpdateSMCMesh();
};

View File

@ -0,0 +1,37 @@
#pragma once
#include "CoreMinimal.h"
#include "Engine/StaticMesh.h"
#include "ProceduralMeshComponent.h"
#include "DynamicMesh3.h"
namespace RTGUtils
{
/**
* Reinitialize the given StaticMesh with the input FDynamicMesh3.
* This calls StaticMesh->BuildFromMeshDescriptions(), which can be used at Runtime (vs StaticMesh->Build() which cannot)
*/
RUNTIMEGEOMETRYUTILS_API void UpdateStaticMeshFromDynamicMesh(
UStaticMesh* StaticMesh,
const FDynamicMesh3* Mesh);
/**
* Initialize a ProceduralMeshComponent with a single section defined by the given FDynamicMesh3.
* @param bUseFaceNormals if true, each triangle is shaded with per-triangle normal instead of split-vertex normals from FDynamicMesh3 overlay
* @param bInitializeUV0 if true, UV0 is initialized, otherwise it is not (set to 0)
* @param bInitializePerVertexColors if true, per-vertex colors on the FDynamicMesh3 are used to initialize vertex colors of the PMC
*/
RUNTIMEGEOMETRYUTILS_API void UpdatePMCFromDynamicMesh_SplitTriangles(
UProceduralMeshComponent* Component,
const FDynamicMesh3* Mesh,
bool bUseFaceNormals,
bool bInitializeUV0,
bool bInitializePerVertexColors,
bool bCreateCollision);
}

View File

@ -0,0 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class FRuntimeGeometryUtilsModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};

View File

@ -0,0 +1,61 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class RuntimeGeometryUtils : ModuleRules
{
public RuntimeGeometryUtils(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"GeometricObjects",
"DynamicMesh",
"ProceduralMeshComponent",
"ModelingComponents",
"RenderCore"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"MeshDescription",
"StaticMeshDescription",
"GeometryAlgorithms",
"MeshConversion",
"MeshUtilities2"
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}

4594
SMC_Build.sln Normal file

File diff suppressed because it is too large Load Diff

41
SMC_Build.uproject Normal file
View File

@ -0,0 +1,41 @@
{
"FileVersion": 3,
"EngineAssociation": "{24A6842D-4991-98A9-1A25-0DBE60CE4641}",
"Category": "",
"Description": "",
"Modules": [
{
"Name": "SMC_Build",
"Type": "Runtime",
"LoadingPhase": "Default",
"AdditionalDependencies": [
"Engine"
]
}
],
"Plugins": [
{
"Name": "MeshUtilities2",
"Enabled": true
},
{
"Name": "ModelingToolsEditorMode",
"Enabled": false
},
{
"Name": "MeshModelingToolset",
"Enabled": false
},
{
"Name": "RuntimeGeometryUtils",
"Enabled": true
},
{
"Name": "Bridge",
"Enabled": false,
"SupportedTargetPlatforms": [
"Win64"
]
}
]
}

View File

@ -0,0 +1,14 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class SMC_BuildTarget : TargetRules
{
public SMC_BuildTarget( TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.Latest;
ExtraModuleNames.AddRange( new string[] { "SMC_Build" } );
}
}

View File

@ -0,0 +1,15 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyBlueprintFunctionLibrary.h"
void UMyBlueprintFunctionLibrary::MarkRenderStateDirty(UStaticMeshComponent* Comp)
{
// Comp->MarkRenderStateDirty();
Comp->GetScene()->UpdatePrimitiveDistanceFieldSceneData_GameThread(Comp);
}
void UMyBlueprintFunctionLibrary::MarkRenderDynamicDataDirty(UStaticMeshComponent* Comp)
{
Comp->MarkRenderDynamicDataDirty();
}

View File

@ -0,0 +1,23 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyBlueprintFunctionLibrary.generated.h"
/**
*
*/
UCLASS()
class SMC_BUILD_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
static void MarkRenderStateDirty(UStaticMeshComponent* Comp);
UFUNCTION(BlueprintCallable)
static void MarkRenderDynamicDataDirty(UStaticMeshComponent* Comp);
};

View File

@ -0,0 +1,15 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "SMCGameInstance.h"
// #include "IMeshReductionManagerModule.h"
// #include "MeshUtilities.h"
void USMCGameInstance::Shutdown()
{
}
void USMCGameInstance::OnStart()
{
}

View File

@ -0,0 +1,21 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "SMCGameInstance.generated.h"
/**
*
*/
UCLASS()
class SMC_BUILD_API USMCGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
virtual void Shutdown() override;
protected:
virtual void OnStart() override;
};

View File

@ -0,0 +1,27 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class SMC_Build : ModuleRules
{
public SMC_Build(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[]
{
"Core", "CoreUObject", "Engine", "InputCore",
"DynamicMesh",
});
PrivateDependencyModuleNames.AddRange(new string[] { });
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}

View File

@ -0,0 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "SMC_Build.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, SMC_Build, "SMC_Build" );

View File

@ -0,0 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"

View File

@ -0,0 +1,14 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class SMC_BuildEditorTarget : TargetRules
{
public SMC_BuildEditorTarget( TargetInfo Target) : base(Target)
{
Type = TargetType.Editor;
DefaultBuildSettings = BuildSettingsVersion.Latest;
ExtraModuleNames.AddRange( new string[] { "SMC_Build" } );
}
}

12
修改引擎.md Normal file
View File

@ -0,0 +1,12 @@
class ENGINE_API FStaticMeshRenderData
struct ENGINE_API FStaticMeshLODResources : public FRefCountBase
FStaticMeshLODResources(bool bAddRef = true);
~FStaticMeshLODResources();
class ENGINE_API FObjectCacheContext
暴露这两个类后,对他们的方法都要取消暴露
索引类改成cpu可访问