2020-07-06 18:58:26 -04:00
|
|
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
|
|
|
|
#include "MeshUtilities.h"
|
|
|
|
|
#include "MeshUtilitiesPrivate.h"
|
|
|
|
|
#include "Components/StaticMeshComponent.h"
|
|
|
|
|
#include "Engine/StaticMesh.h"
|
|
|
|
|
#include "Materials/Material.h"
|
|
|
|
|
#include "RawMesh.h"
|
|
|
|
|
#include "StaticMeshResources.h"
|
|
|
|
|
#include "MeshCardRepresentation.h"
|
|
|
|
|
#include "DistanceFieldAtlas.h"
|
2021-01-20 11:34:55 -04:00
|
|
|
#include "MeshRepresentationCommon.h"
|
2021-07-21 18:05:08 -04:00
|
|
|
#include "Containers/BinaryHeap.h"
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
static TAutoConsoleVariable<int32> CVarCardRepresentationParallelBuild(
|
|
|
|
|
TEXT("r.MeshCardRepresentation.ParallelBuild"),
|
|
|
|
|
1,
|
|
|
|
|
TEXT("Whether to use task for mesh card building."),
|
2022-03-10 20:49:33 -05:00
|
|
|
ECVF_Scalability);
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
namespace MeshCardGen
|
|
|
|
|
{
|
|
|
|
|
int32 constexpr NumAxisAlignedDirections = 6;
|
|
|
|
|
int32 constexpr MaxCardsPerMesh = 32;
|
|
|
|
|
};
|
2020-07-06 18:58:26 -04:00
|
|
|
|
|
|
|
|
class FGenerateCardMeshContext
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
const FString& MeshName;
|
2021-07-21 18:05:08 -04:00
|
|
|
const FEmbreeScene& EmbreeScene;
|
2020-07-06 18:58:26 -04:00
|
|
|
FCardRepresentationData& OutData;
|
|
|
|
|
|
2021-07-21 18:05:08 -04:00
|
|
|
FGenerateCardMeshContext(const FString& InMeshName, const FEmbreeScene& InEmbreeScene, FCardRepresentationData& InOutData) :
|
2020-07-06 18:58:26 -04:00
|
|
|
MeshName(InMeshName),
|
2021-07-21 18:05:08 -04:00
|
|
|
EmbreeScene(InEmbreeScene),
|
2020-07-06 18:58:26 -04:00
|
|
|
OutData(InOutData)
|
|
|
|
|
{}
|
|
|
|
|
};
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
struct FIntBox
|
|
|
|
|
{
|
|
|
|
|
FIntBox()
|
|
|
|
|
: Min(INT32_MAX)
|
|
|
|
|
, Max(-INT32_MAX)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
FIntBox(const FIntVector& InMin, const FIntVector& InMax)
|
|
|
|
|
: Min(InMin)
|
|
|
|
|
, Max(InMax)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
void Init()
|
|
|
|
|
{
|
|
|
|
|
Min = FIntVector(INT32_MAX);
|
|
|
|
|
Max = FIntVector(-INT32_MAX);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Add(const FIntVector& Point)
|
|
|
|
|
{
|
|
|
|
|
Min = FIntVector(FMath::Min(Min.X, Point.X), FMath::Min(Min.Y, Point.Y), FMath::Min(Min.Z, Point.Z));
|
|
|
|
|
Max = FIntVector(FMath::Max(Max.X, Point.X), FMath::Max(Max.Y, Point.Y), FMath::Max(Max.Z, Point.Z));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FIntVector2 GetFaceXY() const
|
|
|
|
|
{
|
|
|
|
|
return FIntVector2(Max.X + 1 - Min.X, Max.Y + 1 - Min.Y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32 GetFaceArea() const
|
|
|
|
|
{
|
|
|
|
|
return (Max.X + 1 - Min.X) * (Max.Y + 1 - Min.Y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Contains(const FIntBox& Other) const
|
|
|
|
|
{
|
|
|
|
|
if (Other.Min.X >= Min.X && Other.Max.X <= Max.X
|
|
|
|
|
&& Other.Min.Y >= Min.Y && Other.Max.Y <= Max.Y
|
|
|
|
|
&& Other.Min.Z >= Min.Z && Other.Max.Z <= Max.Z)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FIntVector GetAxisDistanceFromBox(const FIntBox& Box)
|
|
|
|
|
{
|
|
|
|
|
const FIntVector CenterDelta2 = (Max - Min) - (Box.Max - Box.Min);
|
|
|
|
|
const FIntVector ExtentSum2 = (Max + Min) + (Box.Max + Box.Min);
|
|
|
|
|
|
|
|
|
|
FIntVector AxisDistance;
|
|
|
|
|
AxisDistance.X = FMath::Max(FMath::Abs(CenterDelta2.X) - ExtentSum2.X, 0) / 2;
|
|
|
|
|
AxisDistance.Y = FMath::Max(FMath::Abs(CenterDelta2.Y) - ExtentSum2.Y, 0) / 2;
|
|
|
|
|
AxisDistance.Z = FMath::Max(FMath::Abs(CenterDelta2.Z) - ExtentSum2.Z, 0) / 2;
|
|
|
|
|
return AxisDistance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FIntVector GetAxisDistanceFromPoint(const FIntVector& Point)
|
|
|
|
|
{
|
|
|
|
|
FIntVector AxisDistance;
|
|
|
|
|
AxisDistance.X = FMath::Max(FMath::Max(Min.X - Point.X, Point.X - Max.X), 0);
|
|
|
|
|
AxisDistance.Y = FMath::Max(FMath::Max(Min.Y - Point.Y, Point.Y - Max.Y), 0);
|
|
|
|
|
AxisDistance.Z = FMath::Max(FMath::Max(Min.Z - Point.Z, Point.Z - Max.Z), 0);
|
|
|
|
|
return AxisDistance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FIntVector Min;
|
|
|
|
|
FIntVector Max;
|
|
|
|
|
};
|
|
|
|
|
|
2020-07-06 18:58:26 -04:00
|
|
|
#if USE_EMBREE
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
typedef uint16 FSurfelIndex;
|
|
|
|
|
constexpr FSurfelIndex INVALID_SURFEL_INDEX = UINT16_MAX;
|
|
|
|
|
|
2021-07-21 18:05:08 -04:00
|
|
|
struct FSurfel
|
2020-07-06 18:58:26 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
FIntVector Coord;
|
2022-03-10 20:49:33 -05:00
|
|
|
|
|
|
|
|
// Card's min near plane distance from this surfel
|
2022-01-06 16:44:09 +00:00
|
|
|
int32 MinRayZ;
|
2022-03-10 20:49:33 -05:00
|
|
|
|
|
|
|
|
// Percentage of rays which hit something in this cell
|
|
|
|
|
float Coverage;
|
|
|
|
|
|
|
|
|
|
// Coverage weighted by the visibility of this surfel from outside the mesh, decides how important is to cover this surfel
|
|
|
|
|
float WeightedCoverage;
|
|
|
|
|
|
|
|
|
|
float GetDistanceWeight(const FVector3f AxisDistances) const
|
|
|
|
|
{
|
|
|
|
|
return AxisDistances.X + AxisDistances.Y + AxisDistances.Z / 16.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float GetMinRayZWeight() const
|
|
|
|
|
{
|
|
|
|
|
float Weight = 0.0f;
|
|
|
|
|
if (MinRayZ > 0)
|
|
|
|
|
{
|
|
|
|
|
Weight = FMath::Clamp((16.0f - (Coord.Z - MinRayZ)) / 16.0f, 0.0f, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
return Weight;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
};
|
2021-01-20 11:34:55 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
struct FSurfelScenePerDirection
|
|
|
|
|
{
|
|
|
|
|
TArray<FSurfel> Surfels;
|
|
|
|
|
FLumenCardBuildDebugData DebugData;
|
2022-03-10 20:49:33 -05:00
|
|
|
|
|
|
|
|
void Init()
|
|
|
|
|
{
|
|
|
|
|
DebugData.Init();
|
|
|
|
|
Surfels.Reset();
|
|
|
|
|
}
|
2022-01-06 16:44:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct FSurfelScene
|
|
|
|
|
{
|
|
|
|
|
FSurfelScenePerDirection Directions[MeshCardGen::NumAxisAlignedDirections];
|
2022-03-10 20:49:33 -05:00
|
|
|
int32 NumSurfels = 0;
|
2022-01-06 16:44:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct FAxisAlignedDirectionBasis
|
|
|
|
|
{
|
|
|
|
|
FMatrix44f LocalToWorldRotation;
|
|
|
|
|
FVector3f LocalToWorldOffset;
|
|
|
|
|
FIntVector VolumeSize;
|
|
|
|
|
float VoxelSize;
|
|
|
|
|
|
|
|
|
|
FVector3f TransformSurfel(FIntVector SurfelCoord) const
|
|
|
|
|
{
|
|
|
|
|
return LocalToWorldRotation.TransformPosition(FVector3f(SurfelCoord.X + 0.5f, SurfelCoord.Y + 0.5f, SurfelCoord.Z)) * VoxelSize + LocalToWorldOffset;
|
|
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
};
|
2021-01-20 11:34:55 -04:00
|
|
|
|
2021-07-21 18:05:08 -04:00
|
|
|
struct FClusteringParams
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
float VoxelSize = 0.0f;
|
|
|
|
|
int32 MaxSurfelDistanceXY = 0;
|
2022-03-10 20:49:33 -05:00
|
|
|
float MinClusterCoverage = 0.0f;
|
2021-07-21 18:05:08 -04:00
|
|
|
float MinDensityPerCluster = 0.0f;
|
2022-01-06 16:44:09 +00:00
|
|
|
int32 MaxLumenMeshCards = 0;
|
2022-03-10 20:49:33 -05:00
|
|
|
bool bDebug = false;
|
|
|
|
|
bool bSingleThreadedBuild = true;
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
FAxisAlignedDirectionBasis ClusterBasis[MeshCardGen::NumAxisAlignedDirections];
|
2021-07-21 18:05:08 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
FVector3f AxisAlignedDirectionIndexToNormal(int32 AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
const int32 AxisIndex = AxisAlignedDirectionIndex / 2;
|
|
|
|
|
|
|
|
|
|
FVector3f Normal(0.0f, 0.0f, 0.0f);
|
|
|
|
|
Normal[AxisIndex] = AxisAlignedDirectionIndex & 1 ? 1.0f : -1.0f;
|
|
|
|
|
return Normal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class FSurfelCluster
|
|
|
|
|
{
|
|
|
|
|
public:
|
2022-01-06 16:44:09 +00:00
|
|
|
FIntBox Bounds;
|
|
|
|
|
TArray<FSurfelIndex> SurfelIndices;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
FIntBox PotentialSurfelsBounds;
|
|
|
|
|
TArray<int32> PotentialSurfels;
|
|
|
|
|
|
|
|
|
|
int32 MinRayZ = 0;
|
2022-03-10 20:49:33 -05:00
|
|
|
float Coverage = 0.0f;
|
|
|
|
|
|
|
|
|
|
// Coverage weighted by visibility
|
|
|
|
|
float WeightedCoverage = 0.0f;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
|
|
|
|
// Best surfels to add to this cluster
|
2022-01-06 16:44:09 +00:00
|
|
|
FSurfelIndex BestSurfelIndex = INVALID_SURFEL_INDEX;
|
|
|
|
|
float BestSurfelDistance = FLT_MAX;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
void Init()
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
Bounds.Init();
|
2022-01-06 16:44:09 +00:00
|
|
|
PotentialSurfelsBounds.Init();
|
2021-07-21 18:05:08 -04:00
|
|
|
SurfelIndices.Reset();
|
2022-01-06 16:44:09 +00:00
|
|
|
PotentialSurfels.Reset();
|
|
|
|
|
MinRayZ = 0;
|
2022-03-10 20:49:33 -05:00
|
|
|
Coverage = 0.0f;
|
|
|
|
|
WeightedCoverage = 0.0f;
|
2022-01-06 16:44:09 +00:00
|
|
|
BestSurfelIndex = INVALID_SURFEL_INDEX;
|
|
|
|
|
BestSurfelDistance = FLT_MAX;
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsValid(const FClusteringParams& ClusteringParams) const
|
|
|
|
|
{
|
2022-03-10 20:49:33 -05:00
|
|
|
return Coverage >= ClusteringParams.MinClusterCoverage
|
2022-01-06 16:44:09 +00:00
|
|
|
&& GetDensity() > ClusteringParams.MinDensityPerCluster;
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
float GetDensity() const
|
|
|
|
|
{
|
2022-03-10 20:49:33 -05:00
|
|
|
const float Density = Coverage / (float)Bounds.GetFaceArea();
|
2022-01-06 16:44:09 +00:00
|
|
|
return Density;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
float GetDensityAfterAdd(const FIntVector& Expand, const FSurfel& Surfel) const
|
2022-01-06 16:44:09 +00:00
|
|
|
{
|
|
|
|
|
FIntVector2 FaceXY = Bounds.GetFaceXY();
|
|
|
|
|
FaceXY.X += Expand.X;
|
|
|
|
|
FaceXY.Y += Expand.Y;
|
|
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
const float Density = (Coverage + Surfel.Coverage) / float(FaceXY.X * FaceXY.Y);
|
2022-01-06 16:44:09 +00:00
|
|
|
return Density;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AddSurfel(const FClusteringParams& ClusteringParams, const FSurfelScenePerDirection& SurfelScene, FSurfelIndex SurfelToAddIndex);
|
|
|
|
|
void UpdateBestSurfel(const FClusteringParams& ClusteringParams, const FSurfelScenePerDirection& SurfelScene, const TBitArray<>& SurfelAssignedToAnyCluster);
|
2021-07-21 18:05:08 -04:00
|
|
|
};
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
void FSurfelCluster::AddSurfel(const FClusteringParams& ClusteringParams, const FSurfelScenePerDirection& SurfelScene, FSurfelIndex SurfelToAddIndex)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
const FSurfel& SurfelToAdd = SurfelScene.Surfels[SurfelToAddIndex];
|
|
|
|
|
SurfelIndices.Add(SurfelToAddIndex);
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
Bounds.Add(SurfelToAdd.Coord);
|
2021-10-20 08:48:24 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
MinRayZ = FMath::Max(MinRayZ, SurfelToAdd.MinRayZ);
|
2022-03-10 20:49:33 -05:00
|
|
|
Coverage += SurfelToAdd.Coverage;
|
|
|
|
|
WeightedCoverage += SurfelToAdd.WeightedCoverage;
|
2022-01-05 10:30:05 -05:00
|
|
|
|
2022-01-05 20:41:17 +00:00
|
|
|
// Check if all surfels are visible after add
|
2022-01-06 16:44:09 +00:00
|
|
|
check(MinRayZ <= Bounds.Min.Z);
|
2022-01-05 20:41:17 +00:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
void FSurfelCluster::UpdateBestSurfel(
|
|
|
|
|
const FClusteringParams& ClusteringParams,
|
|
|
|
|
const FSurfelScenePerDirection& SurfelScene,
|
|
|
|
|
const TBitArray<>& SurfelAssignedToAnyCluster)
|
2022-01-05 20:41:17 +00:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
BestSurfelIndex = INVALID_SURFEL_INDEX;
|
|
|
|
|
BestSurfelDistance = FLT_MAX;
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
// Update potential surfel array if required
|
|
|
|
|
if (!PotentialSurfelsBounds.Contains(Bounds))
|
|
|
|
|
{
|
2022-03-10 20:49:33 -05:00
|
|
|
const FIntVector PotentialSurfelMargin(2, 2, 2);
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
PotentialSurfels.Reset();
|
|
|
|
|
PotentialSurfelsBounds.Min = Bounds.Min - FIntVector(PotentialSurfelMargin);
|
|
|
|
|
PotentialSurfelsBounds.Max = Bounds.Max + FIntVector(PotentialSurfelMargin);
|
|
|
|
|
|
|
|
|
|
for (int32 SurfelIndex = 0; SurfelIndex < SurfelScene.Surfels.Num(); ++SurfelIndex)
|
|
|
|
|
{
|
|
|
|
|
if (!SurfelAssignedToAnyCluster[SurfelIndex])
|
|
|
|
|
{
|
|
|
|
|
const FSurfel& Surfel = SurfelScene.Surfels[SurfelIndex];
|
|
|
|
|
if (Surfel.Coord.Z >= MinRayZ && Surfel.MinRayZ <= Bounds.Min.Z)
|
|
|
|
|
{
|
|
|
|
|
const FIntVector AxisDistances = PotentialSurfelsBounds.GetAxisDistanceFromPoint(Surfel.Coord);
|
2022-03-10 20:49:33 -05:00
|
|
|
if (AxisDistances.X + AxisDistances.Y <= ClusteringParams.MaxSurfelDistanceXY)
|
2022-01-06 16:44:09 +00:00
|
|
|
{
|
|
|
|
|
PotentialSurfels.Add(SurfelIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int32 SurfelIndex : PotentialSurfels)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
if (!SurfelAssignedToAnyCluster[SurfelIndex])
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2021-07-21 18:05:08 -04:00
|
|
|
const FSurfel& Surfel = SurfelScene.Surfels[SurfelIndex];
|
2021-01-20 11:34:55 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
const FIntVector AxisDistances = Bounds.GetAxisDistanceFromPoint(Surfel.Coord);
|
|
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
const float DensityAfterAdd = GetDensityAfterAdd(AxisDistances, Surfel);
|
2022-01-06 16:44:09 +00:00
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
const bool bPassDistanceTest = AxisDistances.X + AxisDistances.Y <= ClusteringParams.MaxSurfelDistanceXY;
|
2022-01-06 16:44:09 +00:00
|
|
|
const bool bPassMinZTest = Surfel.Coord.Z >= MinRayZ && Surfel.MinRayZ <= Bounds.Min.Z;
|
|
|
|
|
const bool bPassDensityTest = DensityAfterAdd > ClusteringParams.MinDensityPerCluster;
|
|
|
|
|
|
|
|
|
|
if (bPassDistanceTest && bPassMinZTest && bPassDensityTest)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
// Weight by distance
|
2022-03-10 20:49:33 -05:00
|
|
|
float SurfelDistance = Surfel.GetDistanceWeight(FVector3f(AxisDistances));
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
// Weight by aspect ratio
|
|
|
|
|
const FIntVector2 FaceXY = Bounds.GetFaceXY();
|
|
|
|
|
if (FaceXY.X > FaceXY.Y && AxisDistances.X < AxisDistances.Y)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
SurfelDistance -= 0.5f;
|
|
|
|
|
}
|
|
|
|
|
else if (FaceXY.X < FaceXY.Y && AxisDistances.X > AxisDistances.Y)
|
|
|
|
|
{
|
|
|
|
|
SurfelDistance -= 0.5f;
|
|
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
// Weight by density
|
|
|
|
|
{
|
2022-03-10 20:49:33 -05:00
|
|
|
SurfelDistance -= DensityAfterAdd;
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
// Weight by MinRayZ
|
2022-01-06 16:44:09 +00:00
|
|
|
if (Surfel.MinRayZ > 0)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-03-10 20:49:33 -05:00
|
|
|
SurfelDistance += Surfel.GetMinRayZWeight();
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
if (SurfelDistance < BestSurfelDistance)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
BestSurfelIndex = SurfelIndex;
|
|
|
|
|
BestSurfelDistance = SurfelDistance;
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
2021-01-20 11:34:55 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-25 19:47:27 -05:00
|
|
|
struct FSurfelSample
|
2022-01-06 16:44:09 +00:00
|
|
|
{
|
|
|
|
|
FVector3f Position;
|
|
|
|
|
FVector3f Normal;
|
|
|
|
|
int32 MinRayZ;
|
2022-02-25 19:47:27 -05:00
|
|
|
int32 CellZ;
|
2022-01-06 16:44:09 +00:00
|
|
|
};
|
|
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
struct FSurfelVisibility
|
|
|
|
|
{
|
|
|
|
|
float Visibility;
|
|
|
|
|
bool bValid;
|
|
|
|
|
};
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
// Trace rays over the hemisphere and discard surfels which mostly hit back faces
|
2022-03-10 20:49:33 -05:00
|
|
|
FSurfelVisibility ComputeSurfelVisibility(
|
2022-01-06 16:44:09 +00:00
|
|
|
const FGenerateCardMeshContext& Context,
|
2022-02-25 19:47:27 -05:00
|
|
|
const TArray<FSurfelSample>& SurfelSamples,
|
|
|
|
|
uint32 SurfelSamplesOffset,
|
|
|
|
|
uint32 SurfelSamplesNum,
|
2022-01-06 16:44:09 +00:00
|
|
|
const TArray<FVector3f>& RayDirectionsOverHemisphere,
|
|
|
|
|
FLumenCardBuildDebugData& DebugData)
|
|
|
|
|
{
|
2022-02-25 19:47:27 -05:00
|
|
|
uint32 SurfelSampleIndex = 0;
|
2022-01-06 16:44:09 +00:00
|
|
|
uint32 NumHits = 0;
|
|
|
|
|
uint32 NumBackFaceHits = 0;
|
|
|
|
|
const float SurfaceRayBias = 0.1f;
|
2022-03-10 20:49:33 -05:00
|
|
|
float VisibilitySum = 0.0f;
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
for (int32 RayIndex = 0; RayIndex < RayDirectionsOverHemisphere.Num(); ++RayIndex)
|
|
|
|
|
{
|
2022-02-25 19:47:27 -05:00
|
|
|
const FSurfelSample& SurfelSample = SurfelSamples[SurfelSampleIndex + SurfelSamplesOffset];
|
|
|
|
|
const FMatrix44f SurfaceBasis = MeshRepresentation::GetTangentBasisFrisvad(SurfelSample.Normal);
|
|
|
|
|
const FVector3f RayOrigin = SurfelSample.Position;
|
2022-01-06 16:44:09 +00:00
|
|
|
const FVector3f RayDirection = SurfaceBasis.TransformVector(RayDirectionsOverHemisphere[RayIndex]);
|
|
|
|
|
|
|
|
|
|
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 = SurfaceRayBias;
|
|
|
|
|
EmbreeRay.ray.tfar = FLT_MAX;
|
|
|
|
|
|
|
|
|
|
FEmbreeIntersectionContext EmbreeContext;
|
|
|
|
|
rtcInitIntersectContext(&EmbreeContext);
|
|
|
|
|
rtcIntersect1(Context.EmbreeScene.EmbreeScene, &EmbreeContext, &EmbreeRay);
|
|
|
|
|
|
|
|
|
|
if (EmbreeRay.hit.geomID != RTC_INVALID_GEOMETRY_ID && EmbreeRay.hit.primID != RTC_INVALID_GEOMETRY_ID)
|
|
|
|
|
{
|
|
|
|
|
++NumHits;
|
2022-02-02 07:59:31 -05:00
|
|
|
if (FVector::DotProduct((FVector)RayDirection, (FVector)EmbreeRay.GetHitNormal()) > 0.0f && !EmbreeContext.IsHitTwoSided())
|
2022-01-06 16:44:09 +00:00
|
|
|
{
|
|
|
|
|
++NumBackFaceHits;
|
|
|
|
|
}
|
2022-03-10 20:49:33 -05:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
VisibilitySum += FMath::Clamp(EmbreeRay.ray.tfar / 1000.0f, 0.0f, 0.5f);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
VisibilitySum += 1.0f;
|
2022-01-06 16:44:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
FLumenCardBuildDebugData::FRay& SurfelRay = DebugData.SurfelRays.AddDefaulted_GetRef();
|
|
|
|
|
SurfelRay.RayStart = RayOrigin;
|
|
|
|
|
SurfelRay.RayEnd = RayOrigin + RayDirection * (EmbreeRay.ray.tfar < FLT_MAX ? EmbreeRay.ray.tfar : 200.0f);
|
|
|
|
|
SurfelRay.bHit = EmbreeRay.ray.tfar < FLT_MAX;
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-02-25 19:47:27 -05:00
|
|
|
SurfelSampleIndex = (SurfelSampleIndex + 1) % SurfelSamplesNum;
|
2022-01-06 16:44:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool bInsideGeometry = NumHits > 0 && NumBackFaceHits > RayDirectionsOverHemisphere.Num() * 0.2f;
|
2022-03-10 20:49:33 -05:00
|
|
|
|
|
|
|
|
FSurfelVisibility SurfelVisibility;
|
|
|
|
|
SurfelVisibility.Visibility = VisibilitySum / RayDirectionsOverHemisphere.Num();
|
|
|
|
|
SurfelVisibility.bValid = !bInsideGeometry;
|
|
|
|
|
return SurfelVisibility;
|
2022-01-06 16:44:09 +00:00
|
|
|
}
|
|
|
|
|
|
2022-02-25 19:47:27 -05:00
|
|
|
/**
|
|
|
|
|
* Voxelize mesh by casting multiple rays per cell
|
|
|
|
|
*/
|
2022-01-06 16:44:09 +00:00
|
|
|
void GenerateSurfelsForDirection(
|
|
|
|
|
const FGenerateCardMeshContext& Context,
|
|
|
|
|
const FAxisAlignedDirectionBasis& ClusterBasis,
|
|
|
|
|
const TArray<FVector3f>& RayDirectionsOverHemisphere,
|
2022-02-25 19:47:27 -05:00
|
|
|
const FClusteringParams& ClusteringParams,
|
2022-01-06 16:44:09 +00:00
|
|
|
FSurfelScenePerDirection& SurfelScenePerDirection)
|
|
|
|
|
{
|
|
|
|
|
const float NormalWeightTreshold = MeshCardRepresentation::GetNormalTreshold();
|
|
|
|
|
const FVector3f RayDirection = ClusterBasis.LocalToWorldRotation.GetScaledAxis(EAxis::Type::Z);
|
|
|
|
|
|
2022-02-25 19:47:27 -05:00
|
|
|
const uint32 NumSurfelSamples = 32;
|
|
|
|
|
const uint32 MinSurfelSamples = 1;
|
|
|
|
|
|
|
|
|
|
TArray<FSurfelSample> SurfelSamples;
|
|
|
|
|
TArray<uint32> NumSurfelSamplesPerCell;
|
|
|
|
|
TArray<uint32> SurfelSamplesOffsetPerCell;
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
for (int32 CoordY = 0; CoordY < ClusterBasis.VolumeSize.Y; ++CoordY)
|
|
|
|
|
{
|
|
|
|
|
for (int32 CoordX = 0; CoordX < ClusterBasis.VolumeSize.X; ++CoordX)
|
|
|
|
|
{
|
2022-02-25 19:47:27 -05:00
|
|
|
SurfelSamples.Reset();
|
|
|
|
|
NumSurfelSamplesPerCell.SetNum(ClusterBasis.VolumeSize.Z);
|
|
|
|
|
SurfelSamplesOffsetPerCell.SetNum(ClusterBasis.VolumeSize.Z);
|
2022-01-06 16:44:09 +00:00
|
|
|
for (int32 CoordZ = 0; CoordZ < ClusterBasis.VolumeSize.Z; ++CoordZ)
|
|
|
|
|
{
|
2022-02-25 19:47:27 -05:00
|
|
|
NumSurfelSamplesPerCell[CoordZ] = 0;
|
|
|
|
|
SurfelSamplesOffsetPerCell[CoordZ] = 0;
|
2022-01-06 16:44:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Trace multiple rays per cell and mark cells which need to spawn a surfel
|
2022-02-25 19:47:27 -05:00
|
|
|
for (uint32 SampleIndex = 0; SampleIndex < NumSurfelSamples; ++SampleIndex)
|
2022-01-06 16:44:09 +00:00
|
|
|
{
|
|
|
|
|
FVector3f Jitter;
|
2022-02-25 19:47:27 -05:00
|
|
|
Jitter.X = (SampleIndex + 0.5f) / NumSurfelSamples;
|
2022-01-06 16:44:09 +00:00
|
|
|
Jitter.Y = (double)ReverseBits(SampleIndex) / (double)0x100000000LL;
|
|
|
|
|
|
2022-02-25 19:47:27 -05:00
|
|
|
FVector3f RayOrigin = ClusterBasis.LocalToWorldRotation.TransformPosition(FVector3f(CoordX + Jitter.X, CoordY + Jitter.Y, 0.0f)) * ClusteringParams.VoxelSize + ClusterBasis.LocalToWorldOffset;
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
int32 LastHitCoordZ = -2;
|
|
|
|
|
while (LastHitCoordZ < ClusterBasis.VolumeSize.Z)
|
|
|
|
|
{
|
|
|
|
|
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;
|
2022-02-25 19:47:27 -05:00
|
|
|
EmbreeRay.ray.tnear = (LastHitCoordZ + 1) * ClusteringParams.VoxelSize;
|
2022-01-06 16:44:09 +00:00
|
|
|
EmbreeRay.ray.tfar = FLT_MAX;
|
|
|
|
|
|
|
|
|
|
FEmbreeIntersectionContext EmbreeContext;
|
|
|
|
|
rtcInitIntersectContext(&EmbreeContext);
|
|
|
|
|
rtcIntersect1(Context.EmbreeScene.EmbreeScene, &EmbreeContext, &EmbreeRay);
|
|
|
|
|
|
|
|
|
|
if (EmbreeRay.hit.geomID != RTC_INVALID_GEOMETRY_ID && EmbreeRay.hit.primID != RTC_INVALID_GEOMETRY_ID)
|
|
|
|
|
{
|
2022-02-25 19:47:27 -05:00
|
|
|
const int32 HitCoordZ = FMath::Clamp(EmbreeRay.ray.tfar / ClusteringParams.VoxelSize, 0, ClusterBasis.VolumeSize.Z - 1);
|
2022-01-06 16:44:09 +00:00
|
|
|
|
2022-02-02 07:59:31 -05:00
|
|
|
FVector SurfaceNormal = (FVector)EmbreeRay.GetHitNormal();
|
|
|
|
|
float NdotD = FVector::DotProduct((FVector)-RayDirection, SurfaceNormal);
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
// Handle two sided hits
|
|
|
|
|
if (NdotD < 0.0f && EmbreeContext.IsHitTwoSided())
|
|
|
|
|
{
|
|
|
|
|
NdotD = -NdotD;
|
|
|
|
|
SurfaceNormal = -SurfaceNormal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool bPassProjectionTest = NdotD >= NormalWeightTreshold;
|
|
|
|
|
if (bPassProjectionTest && HitCoordZ > LastHitCoordZ + 1 && HitCoordZ < ClusterBasis.VolumeSize.Z)
|
|
|
|
|
{
|
2022-02-25 19:47:27 -05:00
|
|
|
FSurfelSample& SurfelSample = SurfelSamples.AddDefaulted_GetRef();
|
|
|
|
|
SurfelSample.Position = RayOrigin + RayDirection * EmbreeRay.ray.tfar;
|
|
|
|
|
SurfelSample.Normal = (FVector3f)SurfaceNormal;
|
|
|
|
|
SurfelSample.CellZ = HitCoordZ;
|
|
|
|
|
SurfelSample.MinRayZ = 0;
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
if (LastHitCoordZ >= 0)
|
|
|
|
|
{
|
2022-02-25 19:47:27 -05:00
|
|
|
SurfelSample.MinRayZ = FMath::Max(SurfelSample.MinRayZ, LastHitCoordZ + 1);
|
2022-01-06 16:44:09 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LastHitCoordZ = HitCoordZ + 1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
LastHitCoordZ = INT32_MAX;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-25 19:47:27 -05:00
|
|
|
// Sort surfel candidates and compact arrays
|
|
|
|
|
{
|
|
|
|
|
struct FSortByZ
|
|
|
|
|
{
|
|
|
|
|
FORCEINLINE bool operator()(const FSurfelSample& A, const FSurfelSample& B) const
|
|
|
|
|
{
|
|
|
|
|
if (A.CellZ != B.CellZ)
|
|
|
|
|
{
|
|
|
|
|
return A.CellZ < B.CellZ;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return A.MinRayZ > B.MinRayZ;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SurfelSamples.Sort(FSortByZ());
|
|
|
|
|
|
|
|
|
|
for (int32 SampleIndex = 0; SampleIndex < SurfelSamples.Num(); ++SampleIndex)
|
|
|
|
|
{
|
|
|
|
|
const FSurfelSample& SurfelSample = SurfelSamples[SampleIndex];
|
|
|
|
|
++NumSurfelSamplesPerCell[SurfelSample.CellZ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int32 CoordZ = 1; CoordZ < ClusterBasis.VolumeSize.Z; ++CoordZ)
|
|
|
|
|
{
|
|
|
|
|
SurfelSamplesOffsetPerCell[CoordZ] = SurfelSamplesOffsetPerCell[CoordZ - 1] + NumSurfelSamplesPerCell[CoordZ - 1];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
// Convert surfel candidates into actual surfels
|
|
|
|
|
for (int32 CoordZ = 0; CoordZ < ClusterBasis.VolumeSize.Z; ++CoordZ)
|
|
|
|
|
{
|
2022-02-25 19:47:27 -05:00
|
|
|
const uint32 CellNumSurfelSamples = NumSurfelSamplesPerCell[CoordZ];
|
|
|
|
|
const uint32 CellSurfelSamplesOffset = SurfelSamplesOffsetPerCell[CoordZ];
|
|
|
|
|
|
|
|
|
|
if (CellNumSurfelSamples >= MinSurfelSamples)
|
2022-01-06 16:44:09 +00:00
|
|
|
{
|
2022-03-10 20:49:33 -05:00
|
|
|
FSurfelVisibility SurfelVisibility = ComputeSurfelVisibility(
|
2022-01-06 16:44:09 +00:00
|
|
|
Context,
|
2022-02-25 19:47:27 -05:00
|
|
|
SurfelSamples,
|
|
|
|
|
CellSurfelSamplesOffset,
|
|
|
|
|
CellNumSurfelSamples,
|
2022-01-06 16:44:09 +00:00
|
|
|
RayDirectionsOverHemisphere,
|
|
|
|
|
SurfelScenePerDirection.DebugData);
|
|
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
if (SurfelVisibility.bValid)
|
2022-01-06 16:44:09 +00:00
|
|
|
{
|
2022-02-25 19:47:27 -05:00
|
|
|
const int32 MedianMinRayZ = SurfelSamples[CellSurfelSamplesOffset + CellNumSurfelSamples / 2].MinRayZ;
|
2022-03-10 20:49:33 -05:00
|
|
|
const float Coverage = CellNumSurfelSamples / float(NumSurfelSamples);
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
FSurfel& Surfel = SurfelScenePerDirection.Surfels.AddDefaulted_GetRef();
|
|
|
|
|
Surfel.Coord = FIntVector(CoordX, CoordY, CoordZ);
|
|
|
|
|
Surfel.MinRayZ = MedianMinRayZ;
|
2022-03-10 20:49:33 -05:00
|
|
|
Surfel.Coverage = Coverage;
|
|
|
|
|
Surfel.WeightedCoverage = Coverage * ((SurfelVisibility.Visibility + 0.5f) / 1.5f);
|
2022-02-25 19:47:27 -05:00
|
|
|
check(Surfel.Coord.Z > Surfel.MinRayZ || Surfel.MinRayZ == 0);
|
2022-01-06 16:44:09 +00:00
|
|
|
}
|
|
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
if (ClusteringParams.bDebug)
|
2022-01-06 16:44:09 +00:00
|
|
|
{
|
|
|
|
|
FLumenCardBuildDebugData::FSurfel& DebugSurfel = SurfelScenePerDirection.DebugData.Surfels.AddDefaulted_GetRef();
|
|
|
|
|
DebugSurfel.Position = ClusterBasis.TransformSurfel(FIntVector(CoordX, CoordY, CoordZ));
|
|
|
|
|
DebugSurfel.Normal = -RayDirection;
|
|
|
|
|
DebugSurfel.SourceSurfelIndex = SurfelScenePerDirection.Surfels.Num() - 1;
|
2022-03-10 20:49:33 -05:00
|
|
|
DebugSurfel.Type = SurfelVisibility.bValid ? FLumenCardBuildDebugData::ESurfelType::Valid : FLumenCardBuildDebugData::ESurfelType::Invalid;
|
2022-01-06 16:44:09 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
void InitClusteringParams(FClusteringParams& ClusteringParams, const FBox& MeshCardsBounds, int32 MaxVoxels, int32 MaxLumenMeshCards, float MinClusterCoverage)
|
|
|
|
|
{
|
|
|
|
|
const float TargetVoxelSize = 20.0f;
|
|
|
|
|
|
|
|
|
|
const FVector3f MeshCardsBoundsSize = 2.0f * (FVector3f)MeshCardsBounds.GetExtent();
|
|
|
|
|
const float MaxMeshCardsBounds = MeshCardsBoundsSize.GetMax();
|
|
|
|
|
|
|
|
|
|
// Target object space detail size
|
|
|
|
|
const float MaxSizeInVoxels = FMath::Clamp(MaxMeshCardsBounds / TargetVoxelSize + 0.5f, 1, MaxVoxels);
|
|
|
|
|
const float VoxelSize = FMath::Max(TargetVoxelSize, MaxMeshCardsBounds / MaxSizeInVoxels);
|
|
|
|
|
|
|
|
|
|
FIntVector SizeInVoxels;
|
|
|
|
|
SizeInVoxels.X = FMath::Clamp(FMath::CeilToFloat(MeshCardsBoundsSize.X / VoxelSize), 1, MaxVoxels);
|
|
|
|
|
SizeInVoxels.Y = FMath::Clamp(FMath::CeilToFloat(MeshCardsBoundsSize.Y / VoxelSize), 1, MaxVoxels);
|
|
|
|
|
SizeInVoxels.Z = FMath::Clamp(FMath::CeilToFloat(MeshCardsBoundsSize.Z / VoxelSize), 1, MaxVoxels);
|
|
|
|
|
|
|
|
|
|
const FVector3f VoxelBoundsCenter = (FVector3f)MeshCardsBounds.GetCenter();
|
|
|
|
|
const FVector3f VoxelBoundsExtent = FVector3f(SizeInVoxels) * VoxelSize * 0.5f;
|
|
|
|
|
const FVector3f VoxelBoundsMin = VoxelBoundsCenter - VoxelBoundsExtent;
|
|
|
|
|
const FVector3f VoxelBoundsMax = VoxelBoundsCenter + VoxelBoundsExtent;
|
|
|
|
|
|
|
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
FAxisAlignedDirectionBasis& ClusterBasis = ClusteringParams.ClusterBasis[AxisAlignedDirectionIndex];
|
|
|
|
|
ClusterBasis.VoxelSize = VoxelSize;
|
|
|
|
|
|
|
|
|
|
FVector3f XAxis = FVector3f(1.0f, 0.0f, 0.0f);
|
|
|
|
|
FVector3f YAxis = FVector3f(0.0f, 1.0f, 0.0f);
|
|
|
|
|
switch (AxisAlignedDirectionIndex / 2)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
XAxis = FVector3f(0.0f, 1.0f, 0.0f);
|
|
|
|
|
YAxis = FVector3f(0.0f, 0.0f, 1.0f);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
|
XAxis = FVector3f(1.0f, 0.0f, 0.0f);
|
|
|
|
|
YAxis = FVector3f(0.0f, 0.0f, 1.0f);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
|
XAxis = FVector3f(1.0f, 0.0f, 0.0f);
|
|
|
|
|
YAxis = FVector3f(0.0f, 1.0f, 0.0f);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FVector3f ZAxis = AxisAlignedDirectionIndexToNormal(AxisAlignedDirectionIndex);
|
|
|
|
|
|
|
|
|
|
ClusterBasis.LocalToWorldRotation = FMatrix44f(XAxis, YAxis, -ZAxis, FVector3f::ZeroVector);
|
|
|
|
|
|
|
|
|
|
ClusterBasis.LocalToWorldOffset = VoxelBoundsMin;
|
|
|
|
|
if (AxisAlignedDirectionIndex & 1)
|
|
|
|
|
{
|
|
|
|
|
ClusterBasis.LocalToWorldOffset[AxisAlignedDirectionIndex / 2] = VoxelBoundsMax[AxisAlignedDirectionIndex / 2];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (AxisAlignedDirectionIndex / 2)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
ClusterBasis.VolumeSize.X = SizeInVoxels.Y;
|
|
|
|
|
ClusterBasis.VolumeSize.Y = SizeInVoxels.Z;
|
|
|
|
|
ClusterBasis.VolumeSize.Z = SizeInVoxels.X;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
|
ClusterBasis.VolumeSize.X = SizeInVoxels.X;
|
|
|
|
|
ClusterBasis.VolumeSize.Y = SizeInVoxels.Z;
|
|
|
|
|
ClusterBasis.VolumeSize.Z = SizeInVoxels.Y;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
|
ClusterBasis.VolumeSize.X = SizeInVoxels.X;
|
|
|
|
|
ClusterBasis.VolumeSize.Y = SizeInVoxels.Y;
|
|
|
|
|
ClusterBasis.VolumeSize.Z = SizeInVoxels.Z;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ClusteringParams.VoxelSize = VoxelSize;
|
|
|
|
|
ClusteringParams.MaxSurfelDistanceXY = MeshCardRepresentation::GetMaxSurfelDistanceXY();
|
|
|
|
|
ClusteringParams.MinClusterCoverage = MinClusterCoverage;
|
|
|
|
|
ClusteringParams.MinDensityPerCluster = MeshCardRepresentation::GetMinDensity();
|
|
|
|
|
ClusteringParams.MaxLumenMeshCards = MaxLumenMeshCards;
|
|
|
|
|
ClusteringParams.bDebug = MeshCardRepresentation::IsDebugMode();
|
|
|
|
|
ClusteringParams.bSingleThreadedBuild = CVarCardRepresentationParallelBuild.GetValueOnAnyThread() == 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InitSurfelScene(
|
2021-07-21 18:05:08 -04:00
|
|
|
const FGenerateCardMeshContext& Context,
|
|
|
|
|
const FBox& MeshCardsBounds,
|
2022-03-10 20:49:33 -05:00
|
|
|
int32 MaxLumenMeshCards,
|
|
|
|
|
float MinClusterCoverageLOD0,
|
|
|
|
|
float MinClusterCoverageLOD1,
|
2021-07-21 18:05:08 -04:00
|
|
|
FSurfelScene& SurfelScene,
|
2022-03-10 20:49:33 -05:00
|
|
|
FClusteringParams& ClusteringParamsLOD0,
|
|
|
|
|
FClusteringParams& ClusteringParamsLOD1)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-02-23 19:25:03 -05:00
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(GenerateSurfels);
|
|
|
|
|
|
2021-07-21 18:05:08 -04:00
|
|
|
const uint32 NumSourceVertices = Context.EmbreeScene.Geometry.VertexArray.Num();
|
|
|
|
|
const uint32 NumSourceIndices = Context.EmbreeScene.Geometry.IndexArray.Num();
|
|
|
|
|
const int32 NumSourceTriangles = NumSourceIndices / 3;
|
|
|
|
|
|
|
|
|
|
if (NumSourceTriangles == 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
2021-01-20 11:34:55 -04:00
|
|
|
}
|
|
|
|
|
|
2021-07-21 18:05:08 -04:00
|
|
|
// Generate random ray directions over a hemisphere
|
|
|
|
|
constexpr uint32 NumRayDirectionsOverHemisphere = 64;
|
|
|
|
|
TArray<FVector3f> RayDirectionsOverHemisphere;
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2021-07-21 18:05:08 -04:00
|
|
|
FRandomStream RandomStream(0);
|
|
|
|
|
MeshUtilities::GenerateStratifiedUniformHemisphereSamples(NumRayDirectionsOverHemisphere, RandomStream, RayDirectionsOverHemisphere);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
const int32 DebugSurfelDirection = MeshCardRepresentation::GetDebugSurfelDirection();
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
// Limit max number of surfels to prevent generation time from exploding, as dense two sided meshes can generate many more surfels than simple walls
|
|
|
|
|
int32 TargetNumSufels = 10000;
|
|
|
|
|
float MaxVoxels = 64;
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
do
|
2022-01-05 20:41:17 +00:00
|
|
|
{
|
2022-03-10 20:49:33 -05:00
|
|
|
InitClusteringParams(ClusteringParamsLOD0, MeshCardsBounds, MaxVoxels, MaxLumenMeshCards, MinClusterCoverageLOD0);
|
|
|
|
|
InitClusteringParams(ClusteringParamsLOD1, MeshCardsBounds, MaxVoxels, MaxLumenMeshCards, MinClusterCoverageLOD1);
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
ParallelFor(TEXT("InitSurfelScene.PF"), MeshCardGen::NumAxisAlignedDirections, 1,
|
|
|
|
|
[&](int32 AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
if (DebugSurfelDirection < 0 || DebugSurfelDirection == AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
FSurfelScenePerDirection& SurfelScenePerDirection = SurfelScene.Directions[AxisAlignedDirectionIndex];
|
|
|
|
|
SurfelScenePerDirection.Init();
|
2022-01-06 16:44:09 +00:00
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
GenerateSurfelsForDirection(
|
|
|
|
|
Context,
|
|
|
|
|
ClusteringParamsLOD1.ClusterBasis[AxisAlignedDirectionIndex],
|
|
|
|
|
RayDirectionsOverHemisphere,
|
|
|
|
|
ClusteringParamsLOD1,
|
|
|
|
|
SurfelScenePerDirection
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}, ClusteringParamsLOD1.bSingleThreadedBuild ? EParallelForFlags::ForceSingleThread : EParallelForFlags::None);
|
2022-01-06 16:44:09 +00:00
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
SurfelScene.NumSurfels = 0;
|
|
|
|
|
for (const FSurfelScenePerDirection& SurfelScenePerDirection : SurfelScene.Directions)
|
2022-01-05 20:41:17 +00:00
|
|
|
{
|
2022-03-10 20:49:33 -05:00
|
|
|
SurfelScene.NumSurfels += SurfelScenePerDirection.Surfels.Num();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MaxVoxels = MaxVoxels / 2;
|
|
|
|
|
} while (SurfelScene.NumSurfels > TargetNumSufels && MaxVoxels > 1);
|
|
|
|
|
|
|
|
|
|
if (ClusteringParamsLOD1.bDebug)
|
|
|
|
|
{
|
|
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
FLumenCardBuildDebugData& MergedDebugData = Context.OutData.MeshCardsBuildData.DebugData;
|
|
|
|
|
FLumenCardBuildDebugData& DirectionDebugData = SurfelScene.Directions[AxisAlignedDirectionIndex].DebugData;
|
|
|
|
|
|
|
|
|
|
const int32 SurfelOffset = MergedDebugData.Surfels.Num();
|
|
|
|
|
|
|
|
|
|
MergedDebugData.Surfels.Append(DirectionDebugData.Surfels);
|
|
|
|
|
MergedDebugData.SurfelRays.Append(DirectionDebugData.SurfelRays);
|
|
|
|
|
|
|
|
|
|
for (FSurfelIndex SurfelIndex = SurfelOffset; SurfelIndex < MergedDebugData.Surfels.Num(); ++SurfelIndex)
|
|
|
|
|
{
|
|
|
|
|
MergedDebugData.Surfels[SurfelIndex].SourceSurfelIndex += SurfelOffset;
|
|
|
|
|
}
|
2022-01-05 20:41:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GrowSingleCluster(
|
|
|
|
|
const FClusteringParams& ClusteringParams,
|
2022-01-06 16:44:09 +00:00
|
|
|
const FSurfelScenePerDirection& SurfelScene,
|
2021-07-21 18:05:08 -04:00
|
|
|
TBitArray<>& SurfelAssignedToAnyCluster,
|
|
|
|
|
FSurfelCluster& Cluster)
|
|
|
|
|
{
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
Cluster.UpdateBestSurfel(ClusteringParams, SurfelScene, SurfelAssignedToAnyCluster);
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
if (Cluster.BestSurfelIndex != INVALID_SURFEL_INDEX)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
Cluster.AddSurfel(ClusteringParams, SurfelScene, Cluster.BestSurfelIndex);
|
|
|
|
|
SurfelAssignedToAnyCluster[Cluster.BestSurfelIndex] = true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
} while (Cluster.BestSurfelIndex != INVALID_SURFEL_INDEX);
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
FSurfelIndex FindBestSeed(const FClusteringParams& ClusteringParams, const FSurfelScenePerDirection& SurfelScene, const FSurfelCluster& Cluster)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
FIntVector CoordSum = FIntVector(0, 0, 0);
|
|
|
|
|
for (FSurfelIndex SurfelIndex : Cluster.SurfelIndices)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
const FSurfel& Surfel = SurfelScene.Surfels[SurfelIndex];
|
2022-01-06 16:44:09 +00:00
|
|
|
CoordSum += Surfel.Coord;
|
2022-01-05 20:41:17 +00:00
|
|
|
}
|
2022-01-05 10:30:05 -05:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
const FVector3f AverageCoord = FVector3f(CoordSum) / Cluster.SurfelIndices.Num();
|
2022-01-05 10:30:05 -05:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
FSurfelIndex BestSurfelIndex = INVALID_SURFEL_INDEX;
|
|
|
|
|
float BestSurfelDistance = FLT_MAX;
|
|
|
|
|
for (FSurfelIndex SurfelIndex : Cluster.SurfelIndices)
|
2022-01-05 20:41:17 +00:00
|
|
|
{
|
|
|
|
|
const FSurfel& Surfel = SurfelScene.Surfels[SurfelIndex];
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
FVector3f AxisDistances = (FVector3f(Surfel.Coord) - AverageCoord).GetAbs();
|
2022-03-10 20:49:33 -05:00
|
|
|
float SurfelDistance = Surfel.GetDistanceWeight(AxisDistances);
|
|
|
|
|
SurfelDistance += Surfel.GetMinRayZWeight();
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
if (SurfelDistance < BestSurfelDistance)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
BestSurfelIndex = SurfelIndex;
|
2022-01-06 16:44:09 +00:00
|
|
|
BestSurfelDistance = SurfelDistance;
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return BestSurfelIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GrowAllClusters(
|
|
|
|
|
const FClusteringParams& ClusteringParams,
|
2022-01-06 16:44:09 +00:00
|
|
|
int32 AxisAlignedDirectionIndex,
|
|
|
|
|
const FSurfelScenePerDirection& SurfelScene,
|
|
|
|
|
TBitArray<>& SurfelAssignedToAnyCluster,
|
|
|
|
|
TArray<FSurfelCluster>& Clusters)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
// Reset all clusters and find their new best seeds
|
2022-01-06 16:44:09 +00:00
|
|
|
SurfelAssignedToAnyCluster.Init(false, SurfelScene.Surfels.Num());
|
2021-07-21 18:05:08 -04:00
|
|
|
for (FSurfelCluster& Cluster : Clusters)
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
const FSurfelIndex ClusterSeedIndex = FindBestSeed(ClusteringParams, SurfelScene, Cluster);
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
Cluster.Init();
|
2021-07-21 18:05:08 -04:00
|
|
|
Cluster.AddSurfel(ClusteringParams, SurfelScene, ClusterSeedIndex);
|
|
|
|
|
SurfelAssignedToAnyCluster[ClusterSeedIndex] = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (FSurfelCluster& Cluster : Clusters)
|
|
|
|
|
{
|
|
|
|
|
Cluster.UpdateBestSurfel(ClusteringParams, SurfelScene, SurfelAssignedToAnyCluster);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cluster surfels
|
|
|
|
|
int32 BestClusterToAddIndex = -1;
|
2022-01-06 16:44:09 +00:00
|
|
|
float BestSurfelToAddDistance = FLT_MAX;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
BestClusterToAddIndex = -1;
|
2022-01-06 16:44:09 +00:00
|
|
|
BestSurfelToAddDistance = FLT_MAX;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
// Pick best surfel across all clusters
|
2021-07-21 18:05:08 -04:00
|
|
|
for (int32 ClusterIndex = 0; ClusterIndex < Clusters.Num(); ++ClusterIndex)
|
|
|
|
|
{
|
|
|
|
|
FSurfelCluster& Cluster = Clusters[ClusterIndex];
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
if (Cluster.BestSurfelIndex != INVALID_SURFEL_INDEX && SurfelAssignedToAnyCluster[Cluster.BestSurfelIndex])
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
Cluster.UpdateBestSurfel(ClusteringParams, SurfelScene, SurfelAssignedToAnyCluster);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
if (Cluster.BestSurfelIndex != INVALID_SURFEL_INDEX)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
if (Cluster.BestSurfelDistance < BestSurfelToAddDistance)
|
|
|
|
|
{
|
|
|
|
|
BestClusterToAddIndex = ClusterIndex;
|
|
|
|
|
BestSurfelToAddDistance = Cluster.BestSurfelDistance;
|
|
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (BestClusterToAddIndex >= 0)
|
|
|
|
|
{
|
|
|
|
|
FSurfelCluster& Cluster = Clusters[BestClusterToAddIndex];
|
|
|
|
|
|
|
|
|
|
Cluster.AddSurfel(ClusteringParams, SurfelScene, Cluster.BestSurfelIndex);
|
|
|
|
|
SurfelAssignedToAnyCluster[Cluster.BestSurfelIndex] = true;
|
|
|
|
|
|
|
|
|
|
Cluster.UpdateBestSurfel(ClusteringParams, SurfelScene, SurfelAssignedToAnyCluster);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} while (BestClusterToAddIndex >= 0);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
struct FMeshCardsLODLevelPerDirection
|
|
|
|
|
{
|
|
|
|
|
TArray<FSurfelCluster> Clusters;
|
|
|
|
|
bool bNeedsToReGrow = false;
|
|
|
|
|
};
|
2021-07-21 18:05:08 -04:00
|
|
|
|
|
|
|
|
struct FMeshCardsLODLevel
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
FMeshCardsLODLevelPerDirection Directions[MeshCardGen::NumAxisAlignedDirections];
|
|
|
|
|
float WeightedSurfaceCoverage = 0.0f;
|
|
|
|
|
float SurfaceArea = 0.0f;
|
|
|
|
|
int32 NumSurfels = 0;
|
|
|
|
|
int32 NumClusters = 0;
|
|
|
|
|
|
|
|
|
|
bool NeedToReGrow() const
|
|
|
|
|
{
|
|
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
if (Directions[AxisAlignedDirectionIndex].bNeedsToReGrow)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void UpdateLODLevelCoverage(const FSurfelScene& SurfelScene, const FClusteringParams& ClusteringParams, FMeshCardsLODLevel& LODLevel)
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
LODLevel.WeightedSurfaceCoverage = 0.0f;
|
|
|
|
|
LODLevel.SurfaceArea = 0.0f;
|
|
|
|
|
LODLevel.NumSurfels = 0;
|
|
|
|
|
LODLevel.NumClusters = 0;
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
TArray<FSurfelCluster>& Clusters = LODLevel.Directions[AxisAlignedDirectionIndex].Clusters;
|
|
|
|
|
const TArray<FSurfel>& Surfels = SurfelScene.Directions[AxisAlignedDirectionIndex].Surfels;
|
|
|
|
|
|
|
|
|
|
for (FSurfelCluster& Cluster : Clusters)
|
|
|
|
|
{
|
|
|
|
|
LODLevel.WeightedSurfaceCoverage += Cluster.WeightedCoverage;
|
|
|
|
|
LODLevel.SurfaceArea += Cluster.Bounds.GetFaceArea();
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
LODLevel.NumSurfels += Surfels.Num();
|
|
|
|
|
LODLevel.NumClusters += Clusters.Num();
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-25 19:47:27 -05:00
|
|
|
/**
|
|
|
|
|
* Cluster only by direction, trying to represent mesh by up to 6 axis aligned projections (like a cubemap)
|
|
|
|
|
*/
|
2021-07-21 18:05:08 -04:00
|
|
|
void BuildMeshCardsLOD0(const FBox& MeshBounds, const FGenerateCardMeshContext& Context, const FSurfelScene& SurfelScene, const FClusteringParams& ClusteringParams, FMeshCardsLODLevel& LODLevel)
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
const TArray<FSurfel>& Surfels = SurfelScene.Directions[AxisAlignedDirectionIndex].Surfels;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
FSurfelCluster TempCluster;
|
|
|
|
|
TempCluster.Init();
|
|
|
|
|
|
|
|
|
|
for (FSurfelIndex SurfelIndex = 0; SurfelIndex < Surfels.Num(); ++SurfelIndex)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
const FSurfel& Surfel = Surfels[SurfelIndex];
|
|
|
|
|
if (Surfel.MinRayZ == 0)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
TempCluster.SurfelIndices.Add(SurfelIndex);
|
2022-01-06 16:44:09 +00:00
|
|
|
TempCluster.Bounds.Add(Surfel.Coord);
|
2022-03-10 20:49:33 -05:00
|
|
|
TempCluster.Coverage += Surfel.Coverage;
|
|
|
|
|
TempCluster.WeightedCoverage += Surfel.WeightedCoverage;
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (TempCluster.IsValid(ClusteringParams))
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
LODLevel.Directions[AxisAlignedDirectionIndex].Clusters.Add(TempCluster);
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UpdateLODLevelCoverage(SurfelScene, ClusteringParams, LODLevel);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
// Process cluster seeds one by one and try to grow clusters from each one of them
|
|
|
|
|
void GenerateClustersFromSeeds(
|
|
|
|
|
const FClusteringParams& ClusteringParams,
|
|
|
|
|
int32 AxisAlignedDirectionIndex,
|
|
|
|
|
const FSurfelScenePerDirection& SurfelScene,
|
|
|
|
|
TBitArray<>& SurfelAssignedToAnyCluster,
|
|
|
|
|
TArray<FSurfelIndex>& ClusterSeeds,
|
|
|
|
|
TArray<FSurfelCluster>& Clusters)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
const int32 NumSeedIterations = MeshCardRepresentation::GetSeedIterations();
|
2021-07-21 18:05:08 -04:00
|
|
|
|
|
|
|
|
FRandomStream ClusterSeedRandomStream(0);
|
|
|
|
|
FSurfelCluster TempCluster;
|
|
|
|
|
|
|
|
|
|
while (ClusterSeeds.Num() > 0)
|
|
|
|
|
{
|
|
|
|
|
// Select next seed
|
2022-01-06 16:44:09 +00:00
|
|
|
FSurfelIndex NextSeedSurfelIndex = INVALID_SURFEL_INDEX;
|
2021-07-21 18:05:08 -04:00
|
|
|
while (ClusterSeeds.Num() > 0)
|
|
|
|
|
{
|
|
|
|
|
int32 RandomIndex = ClusterSeedRandomStream.RandHelper(ClusterSeeds.Num());
|
2022-01-06 16:44:09 +00:00
|
|
|
const FSurfelIndex ClusterSeed = ClusterSeeds[RandomIndex];
|
2021-07-21 18:05:08 -04:00
|
|
|
ClusterSeeds.RemoveAtSwap(RandomIndex);
|
|
|
|
|
|
|
|
|
|
if (!SurfelAssignedToAnyCluster[ClusterSeed])
|
|
|
|
|
{
|
|
|
|
|
NextSeedSurfelIndex = ClusterSeed;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to build a cluster using selected seed
|
2022-01-06 16:44:09 +00:00
|
|
|
if (NextSeedSurfelIndex != INVALID_SURFEL_INDEX)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
for (int32 ClusteringIterationIndex = 0; ClusteringIterationIndex < NumSeedIterations; ++ClusteringIterationIndex)
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
TempCluster.Init();
|
2021-07-21 18:05:08 -04:00
|
|
|
TempCluster.AddSurfel(ClusteringParams, SurfelScene, NextSeedSurfelIndex);
|
|
|
|
|
SurfelAssignedToAnyCluster[NextSeedSurfelIndex] = true;
|
|
|
|
|
|
|
|
|
|
GrowSingleCluster(
|
|
|
|
|
ClusteringParams,
|
|
|
|
|
SurfelScene,
|
|
|
|
|
SurfelAssignedToAnyCluster,
|
|
|
|
|
TempCluster);
|
|
|
|
|
|
|
|
|
|
// Restore global state
|
2022-01-06 16:44:09 +00:00
|
|
|
for (FSurfelIndex SurfelIndex : TempCluster.SurfelIndices)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2021-07-21 18:05:08 -04:00
|
|
|
SurfelAssignedToAnyCluster[SurfelIndex] = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (TempCluster.IsValid(ClusteringParams))
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
const FSurfelIndex AverageSurfelIndex = FindBestSeed(ClusteringParams, SurfelScene, TempCluster);
|
2021-07-21 18:05:08 -04:00
|
|
|
if (AverageSurfelIndex != NextSeedSurfelIndex)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2021-07-21 18:05:08 -04:00
|
|
|
ClusterSeeds.RemoveSwap(AverageSurfelIndex);
|
|
|
|
|
NextSeedSurfelIndex = AverageSurfelIndex;
|
2021-01-20 11:34:55 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-07-21 18:05:08 -04:00
|
|
|
break;
|
2021-01-20 11:34:55 -04:00
|
|
|
}
|
|
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add new cluster only if it has least MinSurfelsPerCluster points
|
|
|
|
|
if (TempCluster.IsValid(ClusteringParams))
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
Clusters.Add(TempCluster);
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
for (FSurfelIndex SurfelIndex : TempCluster.SurfelIndices)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
SurfelAssignedToAnyCluster[SurfelIndex] = true;
|
|
|
|
|
}
|
2021-01-20 11:34:55 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-01-06 16:44:09 +00:00
|
|
|
}
|
2021-01-20 11:34:55 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
// Limit number of clusters to fit in user provided limit
|
|
|
|
|
void LimitClusters(const FClusteringParams& ClusteringParams, const FSurfelScene& SurfelScene, FMeshCardsLODLevel& LODLevel)
|
|
|
|
|
{
|
|
|
|
|
int32 NumClusters = 0;
|
|
|
|
|
|
|
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
TArray<FSurfelCluster>& Clusters = LODLevel.Directions[AxisAlignedDirectionIndex].Clusters;
|
|
|
|
|
const TArray<FSurfel>& Surfels = SurfelScene.Directions[AxisAlignedDirectionIndex].Surfels;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
struct FSortByClusterWeightedCoverage
|
|
|
|
|
{
|
|
|
|
|
FORCEINLINE bool operator()(const FSurfelCluster& A, const FSurfelCluster& B) const
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
return A.WeightedCoverage > B.WeightedCoverage;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Clusters.Sort(FSortByClusterWeightedCoverage());
|
|
|
|
|
NumClusters += Clusters.Num();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (NumClusters > ClusteringParams.MaxLumenMeshCards)
|
|
|
|
|
{
|
|
|
|
|
float SmallestClusterWeightedCoverage = FLT_MAX;
|
|
|
|
|
int32 SmallestClusterDirectionIndex = 0;
|
|
|
|
|
|
|
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
const TArray<FSurfelCluster>& Clusters = LODLevel.Directions[AxisAlignedDirectionIndex].Clusters;
|
|
|
|
|
if (Clusters.Num() > 0)
|
|
|
|
|
{
|
|
|
|
|
const FSurfelCluster& Cluster = Clusters.Last();
|
|
|
|
|
if (Cluster.WeightedCoverage < SmallestClusterWeightedCoverage)
|
|
|
|
|
{
|
|
|
|
|
SmallestClusterDirectionIndex = AxisAlignedDirectionIndex;
|
|
|
|
|
SmallestClusterWeightedCoverage = Cluster.WeightedCoverage;
|
|
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
FMeshCardsLODLevelPerDirection& MeshCardsLODLevelPerDirection = LODLevel.Directions[SmallestClusterDirectionIndex];
|
|
|
|
|
MeshCardsLODLevelPerDirection.Clusters.Pop();
|
|
|
|
|
MeshCardsLODLevelPerDirection.bNeedsToReGrow = true;
|
|
|
|
|
--NumClusters;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cluster by direction and position
|
|
|
|
|
void BuildMeshCardsLOD1(const FBox& MeshBounds, const FGenerateCardMeshContext& Context, const FSurfelScene& SurfelScene, const FClusteringParams& ClusteringParams, FMeshCardsLODLevel& LODLevel)
|
|
|
|
|
{
|
|
|
|
|
const int32 NumGrowIterations = MeshCardRepresentation::GetGrowIterations();
|
|
|
|
|
|
|
|
|
|
TBitArray<> SurfelAssignedToAnyClusterArray[MeshCardGen::NumAxisAlignedDirections];
|
|
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
SurfelAssignedToAnyClusterArray[AxisAlignedDirectionIndex].Init(false, SurfelScene.Directions[AxisAlignedDirectionIndex].Surfels.Num());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate initial list of clusters
|
2022-03-10 20:49:33 -05:00
|
|
|
ParallelFor(TEXT("BuildMeshCardsLOD1.PF"), MeshCardGen::NumAxisAlignedDirections, 1,
|
2022-01-06 16:44:09 +00:00
|
|
|
[&](int32 AxisAlignedDirectionIndex)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
const FSurfelScenePerDirection& SurfelScenePerDirection = SurfelScene.Directions[AxisAlignedDirectionIndex];
|
|
|
|
|
TArray<FSurfelCluster>& Clusters = LODLevel.Directions[AxisAlignedDirectionIndex].Clusters;
|
|
|
|
|
TBitArray<>& SurfelAssignedToAnyCluster = SurfelAssignedToAnyClusterArray[AxisAlignedDirectionIndex];
|
|
|
|
|
|
|
|
|
|
TArray<FSurfelIndex> ClusterSeeds;
|
|
|
|
|
ClusterSeeds.SetNumUninitialized(SurfelScenePerDirection.Surfels.Num());
|
|
|
|
|
for (FSurfelIndex SurfelIndex = 0; SurfelIndex < SurfelScenePerDirection.Surfels.Num(); ++SurfelIndex)
|
|
|
|
|
{
|
|
|
|
|
ClusterSeeds[SurfelIndex] = SurfelIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GenerateClustersFromSeeds(
|
|
|
|
|
ClusteringParams,
|
|
|
|
|
AxisAlignedDirectionIndex,
|
|
|
|
|
SurfelScenePerDirection,
|
|
|
|
|
SurfelAssignedToAnyCluster,
|
|
|
|
|
ClusterSeeds,
|
|
|
|
|
Clusters
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Grow all clusters simultaneously from the best seed
|
|
|
|
|
for (int32 ClusteringIterationIndex = 0; ClusteringIterationIndex < NumGrowIterations; ++ClusteringIterationIndex)
|
|
|
|
|
{
|
|
|
|
|
GrowAllClusters(
|
|
|
|
|
ClusteringParams,
|
|
|
|
|
AxisAlignedDirectionIndex,
|
|
|
|
|
SurfelScenePerDirection,
|
|
|
|
|
SurfelAssignedToAnyCluster,
|
|
|
|
|
Clusters
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
bool bAnyClusterSeedChanged = false;
|
|
|
|
|
for (FSurfelCluster& Cluster : Clusters)
|
|
|
|
|
{
|
|
|
|
|
const FSurfelIndex ClusterSeedIndex = FindBestSeed(ClusteringParams, SurfelScenePerDirection, Cluster);
|
|
|
|
|
if (ClusterSeedIndex != Cluster.SurfelIndices[0])
|
|
|
|
|
{
|
|
|
|
|
bAnyClusterSeedChanged = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!bAnyClusterSeedChanged)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-03-10 20:49:33 -05:00
|
|
|
}, ClusteringParams.bSingleThreadedBuild ? EParallelForFlags::ForceSingleThread : EParallelForFlags::None);
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
LimitClusters(ClusteringParams, SurfelScene, LODLevel);
|
|
|
|
|
|
|
|
|
|
if (LODLevel.NeedToReGrow())
|
|
|
|
|
{
|
2022-03-10 20:49:33 -05:00
|
|
|
ParallelFor(TEXT("BuildMeshCardsLOD1.PF"), MeshCardGen::NumAxisAlignedDirections, 1,
|
2022-01-06 16:44:09 +00:00
|
|
|
[&](int32 AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
if (LODLevel.Directions[AxisAlignedDirectionIndex].bNeedsToReGrow)
|
|
|
|
|
{
|
|
|
|
|
const FSurfelScenePerDirection& SurfelScenePerDirection = SurfelScene.Directions[AxisAlignedDirectionIndex];
|
|
|
|
|
TArray<FSurfelCluster>& Clusters = LODLevel.Directions[AxisAlignedDirectionIndex].Clusters;
|
|
|
|
|
TBitArray<>& SurfelAssignedToAnyCluster = SurfelAssignedToAnyClusterArray[AxisAlignedDirectionIndex];
|
|
|
|
|
|
|
|
|
|
// Grow all clusters simultaneously from the best seed
|
|
|
|
|
for (int32 ClusteringIterationIndex = 0; ClusteringIterationIndex < NumGrowIterations; ++ClusteringIterationIndex)
|
|
|
|
|
{
|
|
|
|
|
GrowAllClusters(
|
|
|
|
|
ClusteringParams,
|
|
|
|
|
AxisAlignedDirectionIndex,
|
|
|
|
|
SurfelScenePerDirection,
|
|
|
|
|
SurfelAssignedToAnyCluster,
|
|
|
|
|
Clusters
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
bool bAnyClusterSeedChanged = false;
|
|
|
|
|
for (FSurfelCluster& Cluster : Clusters)
|
|
|
|
|
{
|
|
|
|
|
const FSurfelIndex ClusterSeedIndex = FindBestSeed(ClusteringParams, SurfelScenePerDirection, Cluster);
|
|
|
|
|
if (ClusterSeedIndex != Cluster.SurfelIndices[0])
|
|
|
|
|
{
|
|
|
|
|
bAnyClusterSeedChanged = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!bAnyClusterSeedChanged)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-03-10 20:49:33 -05:00
|
|
|
}, ClusteringParams.bSingleThreadedBuild ? EParallelForFlags::ForceSingleThread : EParallelForFlags::None);
|
2021-01-20 11:34:55 -04:00
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
|
|
|
|
|
UpdateLODLevelCoverage(SurfelScene, ClusteringParams, LODLevel);
|
2021-01-20 11:34:55 -04:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
void SerializeLOD(
|
|
|
|
|
const FGenerateCardMeshContext& Context,
|
|
|
|
|
const FClusteringParams& ClusteringParams,
|
|
|
|
|
const FSurfelScene& SurfelScene,
|
|
|
|
|
FMeshCardsLODLevel const& LODLevel,
|
|
|
|
|
int32 LODLevelIndex,
|
|
|
|
|
const FBox& MeshCardsBounds,
|
|
|
|
|
FMeshCardsBuildData& MeshCardsBuildData)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2021-07-21 18:05:08 -04:00
|
|
|
MeshCardsBuildData.MaxLODLevel = FMath::Max(MeshCardsBuildData.MaxLODLevel, LODLevelIndex);
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
int32 SourceSurfelOffset = 0;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
const FAxisAlignedDirectionBasis& ClusterBasis = ClusteringParams.ClusterBasis[AxisAlignedDirectionIndex];
|
|
|
|
|
const FSurfelScenePerDirection& SurfelScenePerDirection = SurfelScene.Directions[AxisAlignedDirectionIndex];
|
|
|
|
|
const TArray<FSurfel>& Surfels = SurfelScenePerDirection.Surfels;
|
|
|
|
|
const TArray<FSurfelCluster>& Clusters = LODLevel.Directions[AxisAlignedDirectionIndex].Clusters;
|
|
|
|
|
|
|
|
|
|
TBitArray<> DebugSurfelInCluster;
|
|
|
|
|
TBitArray<> DebugSurfelInAnyCluster(false, Surfels.Num());
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-02-02 07:59:31 -05:00
|
|
|
const FBox3f LocalMeshCardsBounds = FBox3f(MeshCardsBounds.ShiftBy((FVector)-ClusterBasis.LocalToWorldOffset).TransformBy(FMatrix(ClusterBasis.LocalToWorldRotation.GetTransposed())));
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
for (const FSurfelCluster& Cluster : Clusters)
|
2022-01-05 20:41:17 +00:00
|
|
|
{
|
2022-02-25 19:47:27 -05:00
|
|
|
// Clamp to mesh bounds
|
|
|
|
|
FVector3f ClusterBoundsMin = (FVector3f(Cluster.Bounds.Min) - FVector3f(0.0f, 0.0f, 0.5f)) * ClusteringParams.VoxelSize;
|
|
|
|
|
FVector3f ClusterBoundsMax = (FVector3f(Cluster.Bounds.Max) + FVector3f(1.0f, 1.0f, 1.0f)) * ClusteringParams.VoxelSize;
|
|
|
|
|
ClusterBoundsMin = FVector3f::Max(ClusterBoundsMin, LocalMeshCardsBounds.Min);
|
|
|
|
|
ClusterBoundsMax = FVector3f::Min(ClusterBoundsMax, LocalMeshCardsBounds.Max);
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-02-25 19:47:27 -05:00
|
|
|
const FVector3f ClusterBoundsOrigin = (ClusterBoundsMax + ClusterBoundsMin) * 0.5f;
|
|
|
|
|
const FVector3f ClusterBoundsExtent = (ClusterBoundsMax - ClusterBoundsMin) * 0.5f;
|
|
|
|
|
const FVector3f MeshClusterBoundsOrigin = ClusterBasis.LocalToWorldRotation.TransformPosition(ClusterBoundsOrigin) + ClusterBasis.LocalToWorldOffset;
|
2022-01-06 16:44:09 +00:00
|
|
|
|
2022-02-25 19:47:27 -05:00
|
|
|
FLumenCardBuildData BuiltData;
|
|
|
|
|
BuiltData.OBB.Origin = MeshClusterBoundsOrigin;
|
|
|
|
|
BuiltData.OBB.Extent = ClusterBoundsExtent;
|
|
|
|
|
BuiltData.OBB.AxisX = ClusterBasis.LocalToWorldRotation.GetScaledAxis(EAxis::X);
|
|
|
|
|
BuiltData.OBB.AxisY = ClusterBasis.LocalToWorldRotation.GetScaledAxis(EAxis::Y);
|
|
|
|
|
BuiltData.OBB.AxisZ = -ClusterBasis.LocalToWorldRotation.GetScaledAxis(EAxis::Z);
|
|
|
|
|
BuiltData.LODLevel = LODLevelIndex;
|
|
|
|
|
BuiltData.AxisAlignedDirectionIndex = AxisAlignedDirectionIndex;
|
|
|
|
|
MeshCardsBuildData.CardBuildData.Add(BuiltData);
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
if (ClusteringParams.bDebug)
|
2022-02-25 19:47:27 -05:00
|
|
|
{
|
2022-03-10 20:49:33 -05:00
|
|
|
DebugSurfelInCluster.Reset();
|
|
|
|
|
DebugSurfelInCluster.Add(false, Surfels.Num());
|
2022-02-25 19:47:27 -05:00
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
FLumenCardBuildDebugData::FSurfelCluster& DebugCluster = MeshCardsBuildData.DebugData.Clusters.AddDefaulted_GetRef();
|
|
|
|
|
DebugCluster.Surfels.Reserve(DebugCluster.Surfels.Num() + Surfels.Num());
|
2022-02-25 19:47:27 -05:00
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
// Cluster seed
|
2022-01-05 20:41:17 +00:00
|
|
|
{
|
2022-03-10 20:49:33 -05:00
|
|
|
FLumenCardBuildDebugData::FSurfel DebugSurfel;
|
|
|
|
|
DebugSurfel.Position = ClusterBasis.TransformSurfel(Surfels[Cluster.SurfelIndices[0]].Coord);
|
|
|
|
|
DebugSurfel.Normal = AxisAlignedDirectionIndexToNormal(AxisAlignedDirectionIndex);
|
|
|
|
|
DebugSurfel.SourceSurfelIndex = SourceSurfelOffset + Cluster.SurfelIndices[0];
|
|
|
|
|
DebugSurfel.Type = FLumenCardBuildDebugData::ESurfelType::Seed;
|
|
|
|
|
DebugCluster.Surfels.Add(DebugSurfel);
|
2022-01-05 20:41:17 +00:00
|
|
|
}
|
2022-01-06 16:44:09 +00:00
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
{
|
|
|
|
|
const FSurfelIndex AverageSurfelIndex = FindBestSeed(ClusteringParams, SurfelScenePerDirection, Cluster);
|
2022-01-06 16:44:09 +00:00
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
FLumenCardBuildDebugData::FSurfel DebugSurfel;
|
|
|
|
|
DebugSurfel.Position = ClusterBasis.TransformSurfel(Surfels[AverageSurfelIndex].Coord);
|
|
|
|
|
DebugSurfel.Normal = AxisAlignedDirectionIndexToNormal(AxisAlignedDirectionIndex);
|
|
|
|
|
DebugSurfel.SourceSurfelIndex = SourceSurfelOffset + AverageSurfelIndex;
|
|
|
|
|
DebugSurfel.Type = FLumenCardBuildDebugData::ESurfelType::Seed2;
|
|
|
|
|
DebugCluster.Surfels.Add(DebugSurfel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (FSurfelIndex SurfelIndex : Cluster.SurfelIndices)
|
2022-01-06 16:44:09 +00:00
|
|
|
{
|
|
|
|
|
FLumenCardBuildDebugData::FSurfel DebugSurfel;
|
|
|
|
|
DebugSurfel.Position = ClusterBasis.TransformSurfel(Surfels[SurfelIndex].Coord);
|
|
|
|
|
DebugSurfel.Normal = AxisAlignedDirectionIndexToNormal(AxisAlignedDirectionIndex);
|
|
|
|
|
DebugSurfel.SourceSurfelIndex = SourceSurfelOffset + SurfelIndex;
|
2022-03-10 20:49:33 -05:00
|
|
|
DebugSurfel.Type = FLumenCardBuildDebugData::ESurfelType::Cluster;
|
2022-01-06 16:44:09 +00:00
|
|
|
DebugCluster.Surfels.Add(DebugSurfel);
|
2022-03-10 20:49:33 -05:00
|
|
|
|
|
|
|
|
const int32 SurfelMinRayZ = Surfels[SurfelIndex].MinRayZ;
|
|
|
|
|
if (SurfelMinRayZ > 0)
|
|
|
|
|
{
|
|
|
|
|
FIntVector MinRayZCoord = Surfels[SurfelIndex].Coord;
|
|
|
|
|
MinRayZCoord.Z = SurfelMinRayZ;
|
|
|
|
|
|
|
|
|
|
FLumenCardBuildDebugData::FRay DebugRay;
|
|
|
|
|
DebugRay.RayStart = DebugSurfel.Position;
|
|
|
|
|
DebugRay.RayEnd = ClusterBasis.TransformSurfel(MinRayZCoord);
|
|
|
|
|
DebugRay.bHit = false;
|
|
|
|
|
DebugCluster.Rays.Add(DebugRay);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DebugSurfelInAnyCluster[SurfelIndex] = true;
|
|
|
|
|
DebugSurfelInCluster[SurfelIndex] = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (FSurfelIndex SurfelIndex = 0; SurfelIndex < Surfels.Num(); ++SurfelIndex)
|
|
|
|
|
{
|
|
|
|
|
if (!DebugSurfelInCluster[SurfelIndex])
|
|
|
|
|
{
|
|
|
|
|
FLumenCardBuildDebugData::FSurfel DebugSurfel;
|
|
|
|
|
DebugSurfel.Position = ClusterBasis.TransformSurfel(Surfels[SurfelIndex].Coord);
|
|
|
|
|
DebugSurfel.Normal = AxisAlignedDirectionIndexToNormal(AxisAlignedDirectionIndex);
|
|
|
|
|
DebugSurfel.SourceSurfelIndex = SourceSurfelOffset + SurfelIndex;
|
|
|
|
|
DebugSurfel.Type = DebugSurfelInAnyCluster[SurfelIndex] ? FLumenCardBuildDebugData::ESurfelType::Used : FLumenCardBuildDebugData::ESurfelType::Idle;
|
|
|
|
|
DebugCluster.Surfels.Add(DebugSurfel);
|
|
|
|
|
}
|
2022-01-06 16:44:09 +00:00
|
|
|
}
|
|
|
|
|
}
|
2022-01-05 20:41:17 +00:00
|
|
|
}
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
SourceSurfelOffset += Surfels.Num();
|
2022-01-05 20:41:17 +00:00
|
|
|
}
|
2022-01-06 16:44:09 +00:00
|
|
|
|
2022-03-10 20:49:33 -05:00
|
|
|
if (ClusteringParams.bDebug)
|
2022-02-25 19:47:27 -05:00
|
|
|
{
|
2022-03-10 20:49:33 -05:00
|
|
|
UE_LOG(LogMeshUtilities, Log, TEXT("CardGen Mesh:%s LOD:%d Surfels:%d Clusters:%d WeightedSurfaceCoverage:%f ClusterArea:%f"),
|
|
|
|
|
*Context.MeshName,
|
|
|
|
|
LODLevelIndex,
|
|
|
|
|
LODLevel.NumSurfels,
|
|
|
|
|
LODLevel.NumClusters,
|
|
|
|
|
LODLevel.WeightedSurfaceCoverage,
|
|
|
|
|
LODLevel.SurfaceArea);
|
2022-02-25 19:47:27 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
void BuildMeshCards(const FBox& MeshBounds, const FGenerateCardMeshContext& Context, int32 MaxLumenMeshCards, FCardRepresentationData& OutData)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2021-01-23 01:26:01 -04:00
|
|
|
// Make sure BBox isn't empty and we can generate card representation for it. This handles e.g. infinitely thin planes.
|
2021-02-11 17:59:41 -04:00
|
|
|
const FVector MeshCardsBoundsCenter = MeshBounds.GetCenter();
|
|
|
|
|
const FVector MeshCardsBoundsExtent = FVector::Max(MeshBounds.GetExtent() + 1.0f, FVector(5.0f));
|
|
|
|
|
const FBox MeshCardsBounds(MeshCardsBoundsCenter - MeshCardsBoundsExtent, MeshCardsBoundsCenter + MeshCardsBoundsExtent);
|
2020-07-06 18:58:26 -04:00
|
|
|
|
2021-07-21 18:05:08 -04:00
|
|
|
// Prepare a list of surfels for cluster fitting
|
|
|
|
|
FSurfelScene SurfelScene;
|
2022-03-10 20:49:33 -05:00
|
|
|
FClusteringParams ClusteringParamsLOD0;
|
|
|
|
|
FClusteringParams ClusteringParamsLOD1;
|
|
|
|
|
InitSurfelScene(Context, MeshCardsBounds, MaxLumenMeshCards, /*MinClusterCoverageLOD0*/ 0.5f, /*MinClusterCoverageLOD1*/ 10.0f, SurfelScene, ClusteringParamsLOD0, ClusteringParamsLOD1);
|
2021-07-21 18:05:08 -04:00
|
|
|
|
|
|
|
|
FMeshCardsLODLevel MeshCardsLOD0;
|
|
|
|
|
BuildMeshCardsLOD0(MeshBounds, Context, SurfelScene, ClusteringParamsLOD0, MeshCardsLOD0);
|
|
|
|
|
|
|
|
|
|
FMeshCardsLODLevel MeshCardsLOD1;
|
|
|
|
|
BuildMeshCardsLOD1(MeshBounds, Context, SurfelScene, ClusteringParamsLOD1, MeshCardsLOD1);
|
|
|
|
|
|
2021-01-20 11:34:55 -04:00
|
|
|
OutData.MeshCardsBuildData.Bounds = MeshCardsBounds;
|
2021-07-21 18:05:08 -04:00
|
|
|
OutData.MeshCardsBuildData.MaxLODLevel = 0;
|
2021-01-20 11:34:55 -04:00
|
|
|
OutData.MeshCardsBuildData.CardBuildData.Reset();
|
2020-07-06 18:58:26 -04:00
|
|
|
|
2022-02-25 19:47:27 -05:00
|
|
|
SerializeLOD(Context, ClusteringParamsLOD0, SurfelScene, MeshCardsLOD0, /*LODLevelIndex*/ 0, MeshCardsBounds, OutData.MeshCardsBuildData);
|
2020-07-06 18:58:26 -04:00
|
|
|
|
2021-07-21 18:05:08 -04:00
|
|
|
// Optionally serialize LOD1 if it's of higher quality without exceeding the budget
|
2022-01-06 16:44:09 +00:00
|
|
|
if (MeshCardsLOD1.NumClusters <= MeshCardGen::MaxCardsPerMesh
|
|
|
|
|
&& MeshCardsLOD1.WeightedSurfaceCoverage > MeshCardsLOD0.WeightedSurfaceCoverage)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2022-02-25 19:47:27 -05:00
|
|
|
SerializeLOD(Context, ClusteringParamsLOD1, SurfelScene, MeshCardsLOD1, /*LODLevelIndex*/ 1, MeshCardsBounds, OutData.MeshCardsBuildData);
|
2020-07-06 18:58:26 -04:00
|
|
|
}
|
2022-03-10 20:49:33 -05:00
|
|
|
|
|
|
|
|
OutData.MeshCardsBuildData.DebugData.NumSurfels = 0;
|
|
|
|
|
for (const FSurfelScenePerDirection& SurfelScenePerDirection : SurfelScene.Directions)
|
|
|
|
|
{
|
|
|
|
|
OutData.MeshCardsBuildData.DebugData.NumSurfels += SurfelScenePerDirection.Surfels.Num();
|
|
|
|
|
}
|
2020-07-06 18:58:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif // #if USE_EMBREE
|
|
|
|
|
|
|
|
|
|
bool FMeshUtilities::GenerateCardRepresentationData(
|
|
|
|
|
FString MeshName,
|
2020-09-22 23:04:05 -04:00
|
|
|
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
|
2020-07-06 18:58:26 -04:00
|
|
|
const FStaticMeshLODResources& LODModel,
|
|
|
|
|
class FQueuedThreadPool& ThreadPool,
|
2021-01-20 11:34:55 -04:00
|
|
|
const TArray<FSignedDistanceFieldBuildMaterialData>& MaterialBlendModes,
|
2020-07-06 18:58:26 -04:00
|
|
|
const FBoxSphereBounds& Bounds,
|
|
|
|
|
const FDistanceFieldVolumeData* DistanceFieldVolumeData,
|
2022-01-06 16:44:09 +00:00
|
|
|
int32 MaxLumenMeshCards,
|
2021-01-20 11:34:55 -04:00
|
|
|
bool bGenerateAsIfTwoSided,
|
2020-07-06 18:58:26 -04:00
|
|
|
FCardRepresentationData& OutData)
|
|
|
|
|
{
|
|
|
|
|
#if USE_EMBREE
|
2020-07-28 09:09:18 -04:00
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FMeshUtilities::GenerateCardRepresentationData);
|
2021-01-20 11:34:55 -04:00
|
|
|
const double StartTime = FPlatformTime::Seconds();
|
2020-07-28 09:09:18 -04:00
|
|
|
|
2021-01-20 11:34:55 -04:00
|
|
|
FEmbreeScene EmbreeScene;
|
|
|
|
|
MeshRepresentation::SetupEmbreeScene(MeshName,
|
|
|
|
|
SourceMeshData,
|
|
|
|
|
LODModel,
|
|
|
|
|
MaterialBlendModes,
|
|
|
|
|
bGenerateAsIfTwoSided,
|
|
|
|
|
EmbreeScene);
|
2020-07-06 18:58:26 -04:00
|
|
|
|
2021-01-20 11:34:55 -04:00
|
|
|
if (!EmbreeScene.EmbreeScene)
|
2020-07-06 18:58:26 -04:00
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-21 18:05:08 -04:00
|
|
|
FGenerateCardMeshContext Context(MeshName, EmbreeScene, OutData);
|
2020-07-06 18:58:26 -04:00
|
|
|
|
2022-03-02 15:19:12 -05:00
|
|
|
// Note: must operate on the SDF bounds when available, because SDF generation can expand the mesh's bounds
|
|
|
|
|
const FBox BuildCardsBounds = DistanceFieldVolumeData && DistanceFieldVolumeData->LocalSpaceMeshBounds.IsValid ? DistanceFieldVolumeData->LocalSpaceMeshBounds : Bounds.GetBox();
|
|
|
|
|
BuildMeshCards(BuildCardsBounds, Context, MaxLumenMeshCards, OutData);
|
2020-07-06 18:58:26 -04:00
|
|
|
|
2021-01-20 11:34:55 -04:00
|
|
|
MeshRepresentation::DeleteEmbreeScene(EmbreeScene);
|
|
|
|
|
|
Sparse, narrow band, streamed Mesh Signed Distance Fields
* SDFs are now generated, allocated from the atlas and uploaded in 8^3 bricks (7^3 unique data, half voxel padding).
* Tracing must load the brick index from the indirection table, and only bricks near the surface are stored
* 3 mips are now generated, with the lowest resolution always loaded and the other 2 streamed
* SDFs are now G8 narrow band. Lower resolution mips must be traversed when querying distance to nearest surface far away from the surface
* The Distance Field Brick Atlas is now stored for each FScene and dynamically resized based on needs with a GPU memcopy
* Brick atlas uses a 1d pooled allocator which has no fragmentation and greatly reduces packing waste over the 3d allocator
* Added new indirection for Distance Field Asset data, so that only a single entry needs to be updated when a mip is streamed in or out in scenes with millions of instances
* Compute shaders operating on distance field instances generate streaming requests, which are async read back to CPU, turned into IO requests, which are polled and when complete uploaded to atlases
* Any mesh instance inside the Global SDF extent (200m) requests mip1, and at 50m requests mip2
* Now using a batched compute scatter to upload to the distance field atlas instead of RHIUpdateTexture3d, to bypass alignment restrictions and per-upload overhead
* Distance Field streaming uses an async task to move Memcpy and IO request overhead off of the Rendering Thread
* Distance Field Visualization now computes a normal from the SDF gradient and does simple lighting to better visualize the scene representation
* Increased r.DistanceFields.MaxPerMeshResolution from 128 to 512, to better represent large objects
* Mesh SDF generation now uses an Embree point query to calculate closest unsigned distance, and then a much smaller set of rays to count backfaces for negative region determination, for a 11x speedup
* Upgraded mesh utilities to Embree 3.12.2 to get point queries
* Fixed wrong transform used for SDF normals in Lumen, causing non-uniformly scaled meshes to have incorrect Surface Cache interpolation
* Fixed Static Mesh materials not getting PostLoaded before SDF build, causing their blend modes to be wrong for the build, which corrupts the DDC. Also included those blend modes in the DDC key.
Original costs on 1080 GTX (full updates on everything and no screen traces)
10.60ms UpdateGlobalDistanceField
3.62ms LumenReflectiveTest.DirectionalLight_1 Shadowmap 1
1.73ms VoxelizeCards Clipmaps=[0,1,2,3]
0.38ms TraceCards 1 dispatch 1 groups
0.51ms TraceCards 1 dispatch 1 groups
Sparse SDF costs
12.06ms UpdateGlobalDistanceField
4.35ms LumenReflectiveTest.DirectionalLight_1 Shadowmap 1
2.30ms VoxelizeCards Clipmaps=[0,1,2,3]
0.69ms TraceCards 1 dispatch 1 groups
0.77ms TraceCards 1 dispatch 1 groups
Tested: TopazEntry PC, Reverb PC and PS5, EngineTests, QAGame, Rift, Frosty P_Construct_WP, FortGPUTestbed
#rb Krzysztof.Narkowicz
#ROBOMERGE-OWNER: Daniel.Wright
#ROBOMERGE-AUTHOR: daniel.wright
#ROBOMERGE-SOURCE: CL 15784493 in //UE5/Release-5.0-EarlyAccess/...
#ROBOMERGE-BOT: STARSHIP (Release-5.0-EarlyAccess -> Main) (v783-15756269)
#ROBOMERGE-CONFLICT from-shelf
[CL 15790658 by Daniel Wright in ue5-main branch]
2021-03-23 22:40:05 -04:00
|
|
|
const float TimeElapsed = (float)(FPlatformTime::Seconds() - StartTime);
|
|
|
|
|
if (TimeElapsed > 1.0f)
|
|
|
|
|
{
|
2022-03-10 20:49:33 -05:00
|
|
|
UE_LOG(LogMeshUtilities, Log, TEXT("Finished mesh card build in %.1fs %s tris:%d surfels:%d"),
|
Sparse, narrow band, streamed Mesh Signed Distance Fields
* SDFs are now generated, allocated from the atlas and uploaded in 8^3 bricks (7^3 unique data, half voxel padding).
* Tracing must load the brick index from the indirection table, and only bricks near the surface are stored
* 3 mips are now generated, with the lowest resolution always loaded and the other 2 streamed
* SDFs are now G8 narrow band. Lower resolution mips must be traversed when querying distance to nearest surface far away from the surface
* The Distance Field Brick Atlas is now stored for each FScene and dynamically resized based on needs with a GPU memcopy
* Brick atlas uses a 1d pooled allocator which has no fragmentation and greatly reduces packing waste over the 3d allocator
* Added new indirection for Distance Field Asset data, so that only a single entry needs to be updated when a mip is streamed in or out in scenes with millions of instances
* Compute shaders operating on distance field instances generate streaming requests, which are async read back to CPU, turned into IO requests, which are polled and when complete uploaded to atlases
* Any mesh instance inside the Global SDF extent (200m) requests mip1, and at 50m requests mip2
* Now using a batched compute scatter to upload to the distance field atlas instead of RHIUpdateTexture3d, to bypass alignment restrictions and per-upload overhead
* Distance Field streaming uses an async task to move Memcpy and IO request overhead off of the Rendering Thread
* Distance Field Visualization now computes a normal from the SDF gradient and does simple lighting to better visualize the scene representation
* Increased r.DistanceFields.MaxPerMeshResolution from 128 to 512, to better represent large objects
* Mesh SDF generation now uses an Embree point query to calculate closest unsigned distance, and then a much smaller set of rays to count backfaces for negative region determination, for a 11x speedup
* Upgraded mesh utilities to Embree 3.12.2 to get point queries
* Fixed wrong transform used for SDF normals in Lumen, causing non-uniformly scaled meshes to have incorrect Surface Cache interpolation
* Fixed Static Mesh materials not getting PostLoaded before SDF build, causing their blend modes to be wrong for the build, which corrupts the DDC. Also included those blend modes in the DDC key.
Original costs on 1080 GTX (full updates on everything and no screen traces)
10.60ms UpdateGlobalDistanceField
3.62ms LumenReflectiveTest.DirectionalLight_1 Shadowmap 1
1.73ms VoxelizeCards Clipmaps=[0,1,2,3]
0.38ms TraceCards 1 dispatch 1 groups
0.51ms TraceCards 1 dispatch 1 groups
Sparse SDF costs
12.06ms UpdateGlobalDistanceField
4.35ms LumenReflectiveTest.DirectionalLight_1 Shadowmap 1
2.30ms VoxelizeCards Clipmaps=[0,1,2,3]
0.69ms TraceCards 1 dispatch 1 groups
0.77ms TraceCards 1 dispatch 1 groups
Tested: TopazEntry PC, Reverb PC and PS5, EngineTests, QAGame, Rift, Frosty P_Construct_WP, FortGPUTestbed
#rb Krzysztof.Narkowicz
#ROBOMERGE-OWNER: Daniel.Wright
#ROBOMERGE-AUTHOR: daniel.wright
#ROBOMERGE-SOURCE: CL 15784493 in //UE5/Release-5.0-EarlyAccess/...
#ROBOMERGE-BOT: STARSHIP (Release-5.0-EarlyAccess -> Main) (v783-15756269)
#ROBOMERGE-CONFLICT from-shelf
[CL 15790658 by Daniel Wright in ue5-main branch]
2021-03-23 22:40:05 -04:00
|
|
|
TimeElapsed,
|
2021-07-21 18:05:08 -04:00
|
|
|
*MeshName,
|
2022-03-10 20:49:33 -05:00
|
|
|
EmbreeScene.NumIndices / 3,
|
|
|
|
|
OutData.MeshCardsBuildData.DebugData.NumSurfels);
|
Sparse, narrow band, streamed Mesh Signed Distance Fields
* SDFs are now generated, allocated from the atlas and uploaded in 8^3 bricks (7^3 unique data, half voxel padding).
* Tracing must load the brick index from the indirection table, and only bricks near the surface are stored
* 3 mips are now generated, with the lowest resolution always loaded and the other 2 streamed
* SDFs are now G8 narrow band. Lower resolution mips must be traversed when querying distance to nearest surface far away from the surface
* The Distance Field Brick Atlas is now stored for each FScene and dynamically resized based on needs with a GPU memcopy
* Brick atlas uses a 1d pooled allocator which has no fragmentation and greatly reduces packing waste over the 3d allocator
* Added new indirection for Distance Field Asset data, so that only a single entry needs to be updated when a mip is streamed in or out in scenes with millions of instances
* Compute shaders operating on distance field instances generate streaming requests, which are async read back to CPU, turned into IO requests, which are polled and when complete uploaded to atlases
* Any mesh instance inside the Global SDF extent (200m) requests mip1, and at 50m requests mip2
* Now using a batched compute scatter to upload to the distance field atlas instead of RHIUpdateTexture3d, to bypass alignment restrictions and per-upload overhead
* Distance Field streaming uses an async task to move Memcpy and IO request overhead off of the Rendering Thread
* Distance Field Visualization now computes a normal from the SDF gradient and does simple lighting to better visualize the scene representation
* Increased r.DistanceFields.MaxPerMeshResolution from 128 to 512, to better represent large objects
* Mesh SDF generation now uses an Embree point query to calculate closest unsigned distance, and then a much smaller set of rays to count backfaces for negative region determination, for a 11x speedup
* Upgraded mesh utilities to Embree 3.12.2 to get point queries
* Fixed wrong transform used for SDF normals in Lumen, causing non-uniformly scaled meshes to have incorrect Surface Cache interpolation
* Fixed Static Mesh materials not getting PostLoaded before SDF build, causing their blend modes to be wrong for the build, which corrupts the DDC. Also included those blend modes in the DDC key.
Original costs on 1080 GTX (full updates on everything and no screen traces)
10.60ms UpdateGlobalDistanceField
3.62ms LumenReflectiveTest.DirectionalLight_1 Shadowmap 1
1.73ms VoxelizeCards Clipmaps=[0,1,2,3]
0.38ms TraceCards 1 dispatch 1 groups
0.51ms TraceCards 1 dispatch 1 groups
Sparse SDF costs
12.06ms UpdateGlobalDistanceField
4.35ms LumenReflectiveTest.DirectionalLight_1 Shadowmap 1
2.30ms VoxelizeCards Clipmaps=[0,1,2,3]
0.69ms TraceCards 1 dispatch 1 groups
0.77ms TraceCards 1 dispatch 1 groups
Tested: TopazEntry PC, Reverb PC and PS5, EngineTests, QAGame, Rift, Frosty P_Construct_WP, FortGPUTestbed
#rb Krzysztof.Narkowicz
#ROBOMERGE-OWNER: Daniel.Wright
#ROBOMERGE-AUTHOR: daniel.wright
#ROBOMERGE-SOURCE: CL 15784493 in //UE5/Release-5.0-EarlyAccess/...
#ROBOMERGE-BOT: STARSHIP (Release-5.0-EarlyAccess -> Main) (v783-15756269)
#ROBOMERGE-CONFLICT from-shelf
[CL 15790658 by Daniel Wright in ue5-main branch]
2021-03-23 22:40:05 -04:00
|
|
|
}
|
2020-07-06 18:58:26 -04:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
#else
|
|
|
|
|
UE_LOG(LogMeshUtilities, Warning, TEXT("Platform did not set USE_EMBREE, GenerateCardRepresentationData failed."));
|
|
|
|
|
return false;
|
|
|
|
|
#endif
|
|
|
|
|
}
|