CelticCraft/Plugins/VoxelFree/Source/Voxel/Private/VoxelRender/Renderers/VoxelRendererMeshHandler.cpp

462 lines
12 KiB
C++
Raw Permalink Normal View History

2023-07-03 16:17:13 +00:00
// Copyright 2020 Phyronnaz
#include "VoxelRendererMeshHandler.h"
#include "VoxelUtilities/VoxelMathUtilities.h"
#include "VoxelRender/VoxelProceduralMeshComponent.h"
#include "VoxelRender/IVoxelRenderer.h"
#include "VoxelRender/VoxelRenderUtilities.h"
DECLARE_DWORD_ACCUMULATOR_STAT(TEXT("Voxel Proc Mesh Pool"), STAT_VoxelProcMeshPool, STATGROUP_VoxelCounters);
DECLARE_DWORD_ACCUMULATOR_STAT(TEXT("Voxel Proc Mesh Frozen Pool"), STAT_VoxelProcMeshFrozenPool, STATGROUP_VoxelCounters);
DECLARE_DWORD_ACCUMULATOR_STAT(TEXT("Voxel Proc Mesh Used"), STAT_VoxelProcMeshUsed, STATGROUP_VoxelCounters);
TAutoConsoleVariable<int32> CVarLogActionQueue(
TEXT("voxel.renderer.LogMeshActionQueue"),
0,
TEXT("If true, will log every queued action when processed"),
ECVF_Default);
static TAutoConsoleVariable<int32> CVarLogMeshPositionsPrecisionsErrors(
TEXT("voxel.renderer.LogMeshPositionsPrecisionsErrors"),
0,
TEXT("If true, will log mesh positions precisions errors"),
ECVF_Default);
IVoxelRendererMeshHandler::IVoxelRendererMeshHandler(IVoxelRenderer& Renderer)
: Renderer(Renderer)
{
}
IVoxelRendererMeshHandler::~IVoxelRendererMeshHandler()
{
VOXEL_FUNCTION_COUNTER();
check(bIsInit);
ensure(bIsDestroying);
// Avoid crashes
if (GExitPurge) return;
#if CHECK_CHUNK_IDS
ensure(ValidIndices.Num() == 0);
#endif
DEC_DWORD_STAT_BY(STAT_VoxelProcMeshUsed, ActiveMeshes.Num());
DEC_DWORD_STAT_BY(STAT_VoxelProcMeshPool, MeshPool.Num());
DEC_DWORD_STAT_BY(STAT_VoxelProcMeshFrozenPool, FrozenMeshPool.Num());
#if VOXEL_DEBUG
{
int32 NumInvalid = 0;
for (auto It = ActiveMeshes.CreateIterator(); It; ++It)
{
if (!It.Key().IsValid())
{
It.RemoveCurrent();
NumInvalid++;
}
}
// Meshes are invalid when closing the editor
// Is raised when recompiling a voxel world BP, so ensureVoxelSlowNoSideEffects and not ensure
// ensureVoxelSlowNoSideEffects(NumInvalid == 0 || GExitPurge);
ensureVoxelSlowNoSideEffects(ActiveMeshes.Num() == 0);
}
#endif
VOXEL_SCOPE_COUNTER("Destroy proc mesh components");
// Destroy all mesh components we are owning
for (auto& Mesh : MeshPool)
{
if (Mesh.IsValid())
{
Mesh->DestroyComponent();
}
}
for (auto& Mesh : FrozenMeshPool)
{
if (Mesh.IsValid())
{
Mesh->DestroyComponent();
}
}
}
IVoxelRendererMeshHandler::FChunkId IVoxelRendererMeshHandler::AddChunk(int32 LOD, const FIntVector& Position)
{
VOXEL_FUNCTION_COUNTER();
const FChunkId Id = AddChunkImpl(LOD, Position);
#if CHECK_CHUNK_IDS
ValidIndices.Add(Id);
#endif
return Id;
}
#if CHECK_CHUNK_IDS
#define CHECK_CHUNK_ID_IMPL(R) if (!ensure(ChunkId.IsValid()) || !ensure(ValidIndices.Contains(ChunkId))) return R;
#define CHECK_CHUNK_ID() CHECK_CHUNK_ID_IMPL(;)
#define CHECK_CHUNK_ID_RETURN() CHECK_CHUNK_ID_IMPL({})
#else
#define CHECK_CHUNK_ID()
#define CHECK_CHUNK_ID_RETURN()
#endif
void IVoxelRendererMeshHandler::UpdateChunk(
FChunkId ChunkId,
const FVoxelChunkSettings& ChunkSettings,
const FVoxelChunkMesh& MainChunk,
const FVoxelChunkMesh* TransitionChunk,
uint8 TransitionsMask)
{
VOXEL_FUNCTION_COUNTER();
CHECK_CHUNK_ID();
ensure(ChunkSettings.bVisible || ChunkSettings.bEnableCollisions || ChunkSettings.bEnableNavmesh);
FAction Action;
Action.Action = EAction::UpdateChunk;
Action.ChunkId = ChunkId;
Action.UpdateChunk().InitialCall.ChunkSettings = ChunkSettings;
Action.UpdateChunk().InitialCall.ChunkSettings.TransitionsMask = TransitionsMask; // Make subclass believe this is the transitions mask
Action.UpdateChunk().InitialCall.MainChunk = &MainChunk;
Action.UpdateChunk().InitialCall.TransitionChunk = TransitionChunk;
ApplyAction(Action);
}
void IVoxelRendererMeshHandler::RemoveChunk(FChunkId ChunkId)
{
VOXEL_FUNCTION_COUNTER();
CHECK_CHUNK_ID();
#if CHECK_CHUNK_IDS
ValidIndices.Remove(ChunkId);
#endif
FAction Action;
Action.Action = EAction::RemoveChunk;
Action.ChunkId = ChunkId;
ApplyAction(Action);
}
void IVoxelRendererMeshHandler::DitherChunk(FChunkId ChunkId, EDitheringType DitheringType)
{
VOXEL_FUNCTION_COUNTER();
CHECK_CHUNK_ID();
FAction Action;
Action.Action = EAction::DitherChunk;
Action.ChunkId = ChunkId;
Action.DitherChunk().DitheringType = DitheringType;
ApplyAction(Action);
}
void IVoxelRendererMeshHandler::ResetDithering(FChunkId ChunkId)
{
VOXEL_FUNCTION_COUNTER();
CHECK_CHUNK_ID();
FAction Action;
Action.Action = EAction::ResetDithering;
Action.ChunkId = ChunkId;
ApplyAction(Action);
}
void IVoxelRendererMeshHandler::SetTransitionsMaskForSurfaceNets(FChunkId ChunkId, uint8 TransitionsMask)
{
VOXEL_FUNCTION_COUNTER();
CHECK_CHUNK_ID();
FAction Action;
Action.Action = EAction::SetTransitionsMaskForSurfaceNets;
Action.ChunkId = ChunkId;
Action.SetTransitionsMaskForSurfaceNets().TransitionsMask = TransitionsMask;
ApplyAction(Action);
}
void IVoxelRendererMeshHandler::HideChunk(FChunkId ChunkId)
{
VOXEL_FUNCTION_COUNTER();
CHECK_CHUNK_ID();
// Clustered renderer does not support those
ensure(Renderer.Settings.bDitherChunks);
FAction Action;
Action.Action = EAction::HideChunk;
Action.ChunkId = ChunkId;
ApplyAction(Action);
}
void IVoxelRendererMeshHandler::ShowChunk(FChunkId ChunkId)
{
VOXEL_FUNCTION_COUNTER();
CHECK_CHUNK_ID();
// Clustered renderer does not support those
ensure(Renderer.Settings.bDitherChunks);
FAction Action;
Action.Action = EAction::ShowChunk;
Action.ChunkId = ChunkId;
ApplyAction(Action);
}
void IVoxelRendererMeshHandler::Tick(double MaxTime)
{
TickHandler();
}
void IVoxelRendererMeshHandler::RecomputeMeshPositions()
{
VOXEL_FUNCTION_COUNTER();
ensure(!bIsDestroying);
for (auto& It : ActiveMeshes)
{
if (ensure(It.Key.IsValid()))
{
SetMeshPosition(*It.Key, It.Value);
}
}
}
void IVoxelRendererMeshHandler::ApplyToAllMeshes(TFunctionRef<void(UVoxelProceduralMeshComponent&)> Lambda)
{
VOXEL_FUNCTION_COUNTER();
ensure(!bIsDestroying);
for (auto& It : ActiveMeshes)
{
if (ensureVoxelSlow(It.Key.IsValid()))
{
Lambda(*It.Key);
}
}
}
void IVoxelRendererMeshHandler::StartDestroying()
{
ensure(!bIsDestroying);
bIsDestroying = true;
}
UVoxelProceduralMeshComponent* IVoxelRendererMeshHandler::GetNewMesh(FChunkId ChunkId, const FIntVector& Position, uint8 LOD)
{
VOXEL_FUNCTION_COUNTER();
ensure(!bIsDestroying);
auto& Settings = Renderer.Settings;
auto* const RootComponent = Settings.RootComponent.Get();
if (!ensureVoxelSlow(RootComponent))
{
return nullptr;
}
UVoxelProceduralMeshComponent* NewMesh = nullptr;
{
while (MeshPool.Num() > 0 && !NewMesh)
{
NewMesh = MeshPool.Pop(false).Get();
ensure(NewMesh != nullptr);
DEC_DWORD_STAT(STAT_VoxelProcMeshPool);
}
if (!NewMesh)
{
NewMesh = NewObject<UVoxelProceduralMeshComponent>(RootComponent, Settings.ProcMeshClass, NAME_None, RF_Transient);
NewMesh->bCastFarShadow = Settings.bCastFarShadow;
NewMesh->SetupAttachment(RootComponent, NAME_None);
auto* Root = Cast<UPrimitiveComponent>(RootComponent);
if (ensure(Root))
{
NewMesh->BodyInstance.CopyRuntimeBodyInstancePropertiesFrom(&Root->BodyInstance);
NewMesh->BodyInstance.SetObjectType(Root->BodyInstance.GetObjectType());
NewMesh->SetGenerateOverlapEvents(Root->GetGenerateOverlapEvents());
}
NewMesh->RegisterComponent();
NewMesh->SetRelativeScale3D(FVector::OneVector * Settings.VoxelSize);
}
}
check(NewMesh);
INC_DWORD_STAT(STAT_VoxelProcMeshUsed);
ensure(!ActiveMeshes.Contains(NewMesh));
ActiveMeshes.Add(NewMesh, Position);
SetMeshPosition(*NewMesh, Position);
const FVoxelIntBox Bounds = FVoxelUtilities::GetBoundsFromPositionAndDepth<RENDER_CHUNK_SIZE>(Position, LOD);
const FVoxelPriorityHandler PriorityHandler(Bounds, Renderer.GetInvokersPositionsForPriorities());
// Set mesh variables
NewMesh->Init(
LOD,
ChunkId.GetDebugValue(),
PriorityHandler,
AsShared(),
Settings);
if (Settings.PlayType == EVoxelPlayType::Game)
{
// Call BP event if user want to do custom stuff
NewMesh->InitChunk(LOD, Bounds);
}
return NewMesh;
}
void IVoxelRendererMeshHandler::RemoveMesh(UVoxelProceduralMeshComponent& Mesh)
{
VOXEL_FUNCTION_COUNTER();
// Avoid crashes
if (GExitPurge) return;
ensure(ActiveMeshes.Remove(&Mesh) == 1);
// Skip an expensive clear when we're destroying
if (!bIsDestroying)
{
Mesh.SetDistanceFieldData(nullptr);
Mesh.ClearSections(EVoxelProcMeshSectionUpdate::UpdateNow);
Mesh.ClearInit();
// Set world location to 0 to avoid precision issues, as SetRelativeLocation calls MoveComponent :(
Mesh.SetUsingAbsoluteLocation(true);
Mesh.SetWorldLocationAndRotationNoPhysics(FVector::ZeroVector, FRotator::ZeroRotator);
Mesh.SetUsingAbsoluteLocation(false);
}
if (UVoxelProceduralMeshComponent::AreVoxelCollisionsFrozen())
{
FrozenMeshPool.Add(&Mesh);
INC_DWORD_STAT(STAT_VoxelProcMeshFrozenPool);
}
else
{
MeshPool.Add(&Mesh);
INC_DWORD_STAT(STAT_VoxelProcMeshPool);
}
DEC_DWORD_STAT(STAT_VoxelProcMeshUsed);
}
TArray<TWeakObjectPtr<UVoxelProceduralMeshComponent>>& IVoxelRendererMeshHandler::CleanUp(TArray<TWeakObjectPtr<UVoxelProceduralMeshComponent>>& Meshes) const
{
const int32 NumInvalid = Meshes.RemoveAll([](auto& Mesh) { return !Mesh.IsValid(); });
// Meshes are invalid when closing the editor
// ensureVoxelSlowNoSideEffects(NumInvalid == 0 || GExitPurge);
return Meshes;
}
FString IVoxelRendererMeshHandler::FAction::ToString() const
{
FString String = FString::Printf(TEXT("ChunkId: %u; Type: "), ChunkId.GetDebugValue());
switch (Action)
{
case EAction::UpdateChunk:
{
String += "UpdateChunk";
break;
}
case EAction::RemoveChunk:
{
String += "RemoveChunk";
break;
}
case EAction::DitherChunk:
{
String += "DitherChunk; DitheringType: ";
switch (DitherChunk().DitheringType)
{
case EDitheringType::SurfaceNets_LowResToHighRes: String += "SurfaceNets_LowResToHighRes"; break;
case EDitheringType::SurfaceNets_HighResToLowRes: String += "SurfaceNets_HighResToLowRes"; break;
case EDitheringType::Classic_DitherIn: String += "Classic_DitherIn"; break;
case EDitheringType::Classic_DitherOut: String += "Classic_DitherOut"; break;
default: ensure(false);
}
break;
}
case EAction::ResetDithering:
{
String += "ResetDithering";
break;
}
case EAction::SetTransitionsMaskForSurfaceNets:
{
String += FString::Printf(TEXT("SetTransitionsMaskForSurfaceNets; TransitionsMask: %d"), SetTransitionsMaskForSurfaceNets().TransitionsMask);
break;
}
case EAction::HideChunk:
{
String += "HideChunk";
break;
}
case EAction::ShowChunk:
{
String += "ShowChunk";
break;
}
default: ensure(false);
}
return String;
}
void IVoxelRendererMeshHandler::Init()
{
check(!bIsInit);
bIsInit = true;
UVoxelProceduralMeshComponent::AddOnFreezeVoxelCollisionChanged(FOnFreezeVoxelCollisionChanged::FDelegate::CreateThreadSafeSP(
this,
&IVoxelRendererMeshHandler::OnFreezeVoxelCollisionChanged));
}
void IVoxelRendererMeshHandler::SetMeshPosition(UVoxelProceduralMeshComponent& Mesh, const FIntVector& Position) const
{
VOXEL_FUNCTION_COUNTER();
ensure(!bIsDestroying);
// TODO errors might add up when we rebase?
Mesh.SetRelativeLocationAndRotation(
Renderer.Settings.GetChunkRelativePosition(Position),
FRotator::ZeroRotator,
false,
nullptr,
ETeleportType::TeleportPhysics);
// If we don't do that the component does not update if Position = 0 0 0 :(
// Probably UE bug?
if (Position == FIntVector(0, 0, 0))
{
Mesh.UpdateComponentToWorld(EUpdateTransformFlags::None, ETeleportType::TeleportPhysics);
}
if (CVarLogMeshPositionsPrecisionsErrors.GetValueOnGameThread() != 0)
{
const auto A = Mesh.GetRelativeTransform().GetTranslation();
const auto B = Renderer.Settings.GetChunkRelativePosition(Position);
const float Error = FVector::Distance(A, B);
if (Error > 0)
{
LOG_VOXEL(Log, TEXT("Distance between theorical and actual mesh position: %6.6f voxels"), Error);
ensure(Error < 1);
}
}
}
void IVoxelRendererMeshHandler::OnFreezeVoxelCollisionChanged(bool bNewFreezeCollisions)
{
if (!bNewFreezeCollisions)
{
// We can reuse all the frozen meshes
// No need to do anything on them: their collisions will be unfrozen automatically by the proc mesh comp
DEC_DWORD_STAT_BY(STAT_VoxelProcMeshFrozenPool, FrozenMeshPool.Num());
INC_DWORD_STAT_BY(STAT_VoxelProcMeshPool, FrozenMeshPool.Num());
MeshPool.Append(MoveTemp(FrozenMeshPool));
}
}