perf(generation): field-exact LOD0 overhead removal; the unknown bucket is Core.dll

Field-preserving floor round (Codex; reviewed by me against the code):
- exact per-(sample, passage) caching of native/generic floor projections and walkable-air
  checks, behind voxel.FloorRound1PassageProjectionCache
- a zero-work disabled path for VoxelDensityProfile::FScopedTimer, unchanged when enabled
- pointer / range-for iteration in the proven-hot tunnel, passage, spatial and candidate loops
  (TArray RangeCheck was ~6.5% of LOD0 samples on the Development binaries the owner plays on)
- FVector2D temporaries replaced with scalar doubles, same operations in the same order
- FSurfaceColumn made trivial, so thread-local construction does no work. Verified safe:
  ComputeSurfaceColumn writes all five outputs unconditionally before any branch, and every read
  of Box.Cols is gated by Box.Computed

Verified: export OBJ byte-identical (b3e5f4c3..9b377); capability 20,830 / 10,909; all 8
interleaved game runs gave identical totals (841 tiles / 669,834 triangles static, 2,348 /
1,985,216 moving, the same as previous rounds). Per-tile identity for the always-on changes still
needs a check against a stored trace; that is queued first in the next round.

Game path (sampler off, two interleaved runs each):
  projection-cache switch alone: neutral (static worker +0.41%, moving -0.05%)
  vs the previous round (cfad3d1, same harness; cross-round numbers reproduced within ~1% here):
    LOD0 generation p50  static 76.6 -> 65.8 ms (-14%), moving 72.4 -> 59.5 ms (-18%)
    LOD0 request p50/p95 moving 79.4/158.5 -> 67.6/137.6 ms
    worker seconds       static 39.2 -> 33.4, moving 113.7 -> 97.9
  The always-on changes carry the gain; they have no switch, so this is a cross-round comparison.

The sampler's ~12.9% "unknown" LOD0 leaf is engine code: UnrealEditor-Core.dll + RVA 0xC080,
without a Core PDB. The old FSurfaceColumn label is gone. Next round names it by its callers.

PERF-SAMPLED.md: corrected a sentence that repeated my false "daytime use" explanation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-13 21:00:34 +02:00
1 parent b839edd3a3
commit ab4bbe3680
9 files changed
+629 -170

No files matched your search

@@ -712,12 +712,16 @@ namespace
float SDFBlendRadius)
{
FVFRoomFloorOwnership Result;
if (ControlPoints.Num() < 2
|| ControlRadii.Num() != ControlPoints.Num())
const int32 ControlPointCount = ControlPoints.Num();
if (ControlPointCount < 2
|| ControlRadii.Num() != ControlPointCount)
{
return Result;
}
const FVector* ControlPointData = ControlPoints.GetData();
const float* ControlRadiusData = ControlRadii.GetData();
const float Fade = FMath::Max(
FMath::Max(SDFBlendRadius, 0.0f) * 0.35f,
1.0f);
@@ -725,8 +729,8 @@ namespace
for (int32 Endpoint = 0; Endpoint < 2; ++Endpoint)
{
const int32 ControlIndex = Endpoint == 0
? 0 : ControlPoints.Num() - 1;
const FVector& Mouth = ControlPoints[ControlIndex];
? 0 : ControlPointCount - 1;
const FVector& Mouth = ControlPointData[ControlIndex];
const float DX = static_cast<float>(Position.X - Mouth.X);
const float DY = static_cast<float>(Position.Y - Mouth.Y);
const float Distance = FMath::Sqrt(DX * DX + DY * DY);
@@ -752,7 +756,7 @@ namespace
continue;
}
const float DefaultFloorZ = static_cast<float>(Mouth.Z)
- FMath::Abs(ControlRadii[ControlIndex]);
- FMath::Abs(ControlRadiusData[ControlIndex]);
const float StoredFloorZ = Endpoint == 0 ? MouthFloorZA : MouthFloorZB;
const float FloorZ = StoredFloorZ > -FLT_MAX
&& FMath::IsFinite(StoredFloorZ)
@@ -815,26 +819,23 @@ namespace
const FVector& B, float RadiusB,
float T, int32 SegmentIndex, int32 NumSegments,
const FCachedTunnel& Tunnel,
const TArray<FTunnelFloorSegmentProfile>* FloorProfiles,
const FTunnelFloorSegmentProfile* FloorProfile,
const FVFRoomFloorOwnership* RoomFloorOwnership)
{
FVFTunnelFloorTerms Terms;
if (FloorProfiles != nullptr
&& FloorProfiles->Num() == NumSegments
&& FloorProfiles->IsValidIndex(SegmentIndex))
if (FloorProfile != nullptr)
{
const FTunnelFloorSegmentProfile& Profile = (*FloorProfiles)[SegmentIndex];
const float ClampedT = FMath::Clamp(T, 0.0f, 1.0f);
const float ProfileT = Profile.NumSteps > 0
const float ProfileT = FloorProfile->NumSteps > 0
? (ClampedT >= 1.0f
? 1.0f
: FMath::FloorToFloat(ClampedT
* static_cast<float>(Profile.NumSteps))
/ static_cast<float>(Profile.NumSteps))
* static_cast<float>(FloorProfile->NumSteps))
/ static_cast<float>(FloorProfile->NumSteps))
: ClampedT;
Terms.BaseFloorZ = FMath::Lerp(
Profile.StartFloorZ, Profile.EndFloorZ, ProfileT);
Terms.ReliefScale = Profile.ReliefScale;
FloorProfile->StartFloorZ, FloorProfile->EndFloorZ, ProfileT);
Terms.ReliefScale = FloorProfile->ReliefScale;
}
else
{
@@ -878,8 +879,10 @@ namespace
RoomFloorOwnership->Weight, 0.0f, 1.0f);
}
const float HorizontalRun = FVector2D(
static_cast<float>(B.X - A.X), static_cast<float>(B.Y - A.Y)).Size();
const double DeltaX = static_cast<double>(static_cast<float>(B.X - A.X));
const double DeltaY = static_cast<double>(static_cast<float>(B.Y - A.Y));
const float HorizontalRun = static_cast<float>(FMath::Sqrt(
DeltaX * DeltaX + DeltaY * DeltaY));
Terms.ReliefEnvelope = 1.0f;
if (SegmentIndex == 0)
{
@@ -926,12 +929,12 @@ namespace
const void* CacheIdentity, int32 TunnelIndex,
bool bEvaluateRelief,
const FVFFloorReliefColumnKey& Column,
const TArray<FTunnelFloorSegmentProfile>* FloorProfiles,
const FTunnelFloorSegmentProfile* FloorProfile,
const FVFRoomFloorOwnership* RoomFloorOwnership)
{
const FVFTunnelFloorTerms Terms = VF_TunnelFloorTerms(
A, RadiusA, B, RadiusB,
T, SegmentIndex, NumSegments, Tunnel, FloorProfiles,
T, SegmentIndex, NumSegments, Tunnel, FloorProfile,
RoomFloorOwnership);
return VF_TunnelFloorFromTerms(
Position, Tunnel, Terms,
@@ -942,10 +945,12 @@ namespace
const FVector& Position, const FVector& A, const FVector& B,
float& OutT, float& OutDistanceSquared)
{
const FVector2D AXY(static_cast<float>(A.X), static_cast<float>(A.Y));
const FVector2D Delta(
static_cast<float>(B.X - A.X), static_cast<float>(B.Y - A.Y));
const float LengthSquared = Delta.SizeSquared();
const double AXY_X = static_cast<double>(static_cast<float>(A.X));
const double AXY_Y = static_cast<double>(static_cast<float>(A.Y));
const double DeltaX = static_cast<double>(static_cast<float>(B.X - A.X));
const double DeltaY = static_cast<double>(static_cast<float>(B.Y - A.Y));
const float LengthSquared = static_cast<float>(
DeltaX * DeltaX + DeltaY * DeltaY);
if (LengthSquared <= KINDA_SMALL_NUMBER)
{
OutT = 0.0f;
@@ -953,13 +958,17 @@ namespace
return false;
}
const FVector2D QueryXY(
static_cast<float>(Position.X), static_cast<float>(Position.Y));
OutT = FMath::Clamp(
FVector2D::DotProduct(QueryXY - AXY, Delta) / LengthSquared,
0.0f, 1.0f);
const FVector2D ClosestXY = AXY + Delta * OutT;
OutDistanceSquared = (QueryXY - ClosestXY).SizeSquared();
const double QueryX = static_cast<double>(static_cast<float>(Position.X));
const double QueryY = static_cast<double>(static_cast<float>(Position.Y));
const double Dot = (QueryX - AXY_X) * DeltaX
+ (QueryY - AXY_Y) * DeltaY;
OutT = FMath::Clamp(Dot / LengthSquared, 0.0f, 1.0f);
const double ClosestX = AXY_X + DeltaX * OutT;
const double ClosestY = AXY_Y + DeltaY * OutT;
const double DistanceX = QueryX - ClosestX;
const double DistanceY = QueryY - ClosestY;
OutDistanceSquared = static_cast<float>(
DistanceX * DistanceX + DistanceY * DistanceY);
return true;
}
@@ -976,22 +985,30 @@ namespace
const FVFRoomFloorOwnership* RoomFloorOwnership)
{
FVFTunnelShapeEvaluation Result;
if (ControlPoints.Num() < 2
|| ControlRadii.Num() != ControlPoints.Num())
const int32 ControlPointCount = ControlPoints.Num();
if (ControlPointCount < 2
|| ControlRadii.Num() != ControlPointCount)
{
return Result;
}
const int32 NumSegments = ControlPoints.Num() - 1;
const int32 NumSegments = ControlPointCount - 1;
const FVector* ControlPointData = ControlPoints.GetData();
const float* ControlRadiusData = ControlRadii.GetData();
const FTunnelFloorSegmentProfile* FloorProfileData = nullptr;
if (FloorProfiles != nullptr && FloorProfiles->Num() == NumSegments)
{
FloorProfileData = FloorProfiles->GetData();
}
const float FloorBlend = FMath::Max(SDFBlendRadius, 0.0f) * 0.35f;
for (int32 SegmentIndex = 0;
SegmentIndex < NumSegments;
++SegmentIndex)
{
const FVector& A = ControlPoints[SegmentIndex];
const FVector& B = ControlPoints[SegmentIndex + 1];
const float RadiusA = ControlRadii[SegmentIndex];
const float RadiusB = ControlRadii[SegmentIndex + 1];
const FVector& A = ControlPointData[SegmentIndex];
const FVector& B = ControlPointData[SegmentIndex + 1];
const float RadiusA = ControlRadiusData[SegmentIndex];
const float RadiusB = ControlRadiusData[SegmentIndex + 1];
float T = 0.0f;
float HorizontalDistanceSquared = FLT_MAX;
@@ -1007,17 +1024,12 @@ namespace
continue;
}
const FTunnelFloorSegmentProfile* Profile = FloorProfileData != nullptr
? FloorProfileData + SegmentIndex : nullptr;
const FVFTunnelFloorTerms FloorTerms = VF_TunnelFloorTerms(
A, RadiusA, B, RadiusB,
T, SegmentIndex, NumSegments, Tunnel, FloorProfiles,
T, SegmentIndex, NumSegments, Tunnel, Profile,
RoomFloorOwnership);
const FTunnelFloorSegmentProfile* Profile = nullptr;
if (FloorProfiles != nullptr
&& FloorProfiles->Num() == NumSegments
&& FloorProfiles->IsValidIndex(SegmentIndex))
{
Profile = &(*FloorProfiles)[SegmentIndex];
}
const float NaturalFloorZ = Profile != nullptr
? FMath::Lerp(
Profile->NaturalStartFloorZ,
@@ -1220,7 +1232,14 @@ namespace
return false;
}
const int32 NumSegments = ControlPoints.Num() - 1;
const int32 ControlPointCount = ControlPoints.Num();
const int32 NumSegments = ControlPointCount - 1;
const FVector* ControlPointData = ControlPoints.GetData();
const float* ControlRadiusData = ControlRadii.GetData();
const TArray<FTunnelFloorSegmentProfile>* FloorProfiles = bWorldChain
? &Tunnel.WorldFloorProfiles : &Tunnel.FloorProfiles;
const FTunnelFloorSegmentProfile* FloorProfileData =
FloorProfiles->Num() == NumSegments ? FloorProfiles->GetData() : nullptr;
float BestDistanceSquared = FLT_MAX;
bool bFound = false;
for (int32 SegmentIndex = 0;
@@ -1231,22 +1250,22 @@ namespace
float DistanceSquared = FLT_MAX;
if (!VF_ProjectTunnelSegmentXY(
Position,
ControlPoints[SegmentIndex], ControlPoints[SegmentIndex + 1],
ControlPointData[SegmentIndex], ControlPointData[SegmentIndex + 1],
T, DistanceSquared)
|| DistanceSquared >= BestDistanceSquared)
{
continue;
}
const float RadiusA = ControlRadii[SegmentIndex];
const float RadiusB = ControlRadii[SegmentIndex + 1];
const float RadiusA = ControlRadiusData[SegmentIndex];
const float RadiusB = ControlRadiusData[SegmentIndex + 1];
OutFloorZ = VF_TunnelFloorAtSegment(
ControlPoints[SegmentIndex], RadiusA,
ControlPoints[SegmentIndex + 1], RadiusB,
ControlPointData[SegmentIndex], RadiusA,
ControlPointData[SegmentIndex + 1], RadiusB,
T, SegmentIndex, NumSegments, Position, Tunnel,
CacheIdentity, TunnelIndex, /*bEvaluateRelief=*/true,
ReliefColumn,
bWorldChain ? &Tunnel.WorldFloorProfiles : &Tunnel.FloorProfiles,
FloorProfileData != nullptr ? FloorProfileData + SegmentIndex : nullptr,
RoomFloorOwnership);
OutSupportRadius = FMath::Max(
FMath::Min(FMath::Abs(RadiusA), FMath::Abs(RadiusB)) - 0.5f,
@@ -1331,8 +1350,9 @@ bool FChunkSDFSpatialIndex::GetRange(
return false;
}
OutBegin = CellOffsets[static_cast<int32>(Slot)];
OutEnd = CellOffsets[static_cast<int32>(Slot + 1)];
const int32* CellOffsetData = CellOffsets.GetData();
OutBegin = CellOffsetData[static_cast<int32>(Slot)];
OutEnd = CellOffsetData[static_cast<int32>(Slot + 1)];
return OutBegin >= 0 && OutEnd >= OutBegin && OutEnd <= ItemIndices.Num();
}
@@ -1546,9 +1566,10 @@ namespace
int32 CandidateCount = 0;
if (bUseSpatialIndex && Index.GetRange(WorldX, WorldY, Begin, End))
{
const int32* ItemIndexData = Index.ItemIndices.GetData();
for (int32 Cursor = Begin; Cursor < End; ++Cursor)
{
const int32 ItemIndex = Index.ItemIndices[Cursor];
const int32 ItemIndex = ItemIndexData[Cursor];
if (ItemIndex >= 0 && ItemIndex < ItemCount)
{
++CandidateCount;
@@ -7354,6 +7375,9 @@ float VoxelCaveMorphology::EvaluateSDFCached(
const FVector Pos(WorldX, WorldY, WorldZ);
const FVFFloorReliefColumnKey ReliefColumn =
VF_MakeFloorReliefColumnKey(Pos.X, Pos.Y);
const FCachedRoom* RoomData = Cache.Rooms.GetData();
const FCachedRoomFloorJoin* RoomFloorJoinData = Cache.RoomFloorJoins.GetData();
const FCachedTunnel* TunnelData = Cache.Tunnels.GetData();
// Track which room contributes the smallest (most-inside) raw SDF.
// This is used by the terrain ops system to find the "owning" room for
@@ -7367,7 +7391,7 @@ float VoxelCaveMorphology::EvaluateSDFCached(
//=========================================================================
auto EvaluateRoom = [&](int32 RoomIdx)
{
const FCachedRoom& Room = Cache.Rooms[RoomIdx];
const FCachedRoom& Room = RoomData[RoomIdx];
// --- DISTANCE CULL ---
const float DistSq = VF_FloatDistSquared(WorldX, WorldY, WorldZ, Room.Center);
@@ -7476,7 +7500,7 @@ float VoxelCaveMorphology::EvaluateSDFCached(
// not participate in nearest-room terrain-op ownership.
auto EvaluateJoin = [&](int32 JoinIdx)
{
const FCachedRoomFloorJoin& Join = Cache.RoomFloorJoins[JoinIdx];
const FCachedRoomFloorJoin& Join = RoomFloorJoinData[JoinIdx];
const float DistSq = VF_FloatDistSquared(WorldX, WorldY, WorldZ, Join.BoundCenter);
if (DistSq > Join.BoundRadiusSq) return;
@@ -7497,7 +7521,7 @@ float VoxelCaveMorphology::EvaluateSDFCached(
//=========================================================================
auto EvaluateTunnel = [&](int32 TunnelIdx)
{
const FCachedTunnel& Tunnel = Cache.Tunnels[TunnelIdx];
const FCachedTunnel& Tunnel = TunnelData[TunnelIdx];
if (Tunnel.SDFInfluenceRadius > 0.0f
&& VF_DistanceSquaredToAabb(
WorldX, WorldY, WorldZ,
@@ -7555,10 +7579,11 @@ float VoxelCaveMorphology::EvaluateTunnelCoreSDF(
{
const FVector Pos(WorldX, WorldY, WorldZ);
float MinSDF = FLT_MAX;
const FCachedTunnel* TunnelData = Cache.Tunnels.GetData();
auto EvaluateTunnel = [&](int32 TunnelIdx)
{
const FCachedTunnel& Tunnel = Cache.Tunnels[TunnelIdx];
const FCachedTunnel& Tunnel = TunnelData[TunnelIdx];
if (Tunnel.SDFInfluenceRadius > 0.0f
&& VF_DistanceSquaredToAabb(
WorldX, WorldY, WorldZ,
@@ -7597,6 +7622,7 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
VoxelDensityProfile::EBucket::TunnelCoreWorld);
const FVector Pos(WorldX, WorldY, WorldZ);
FTunnelCoreWorldEvaluation Result;
const FCachedTunnel* TunnelData = Cache.Tunnels.GetData();
if (SupportColumn != nullptr && VoxelDensityProfile::AreCountersEnabled())
{
@@ -7605,7 +7631,7 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
}
auto EvaluateTunnel = [&](int32 TunnelIdx)
{
const FCachedTunnel& Tunnel = Cache.Tunnels[TunnelIdx];
const FCachedTunnel& Tunnel = TunnelData[TunnelIdx];
const bool bHasWorldChain = Tunnel.WorldControlPoints.Num() >= 2
&& Tunnel.WorldControlRadii.Num() == Tunnel.WorldControlPoints.Num();
const FVector& BoundCenter = bHasWorldChain
@@ -7731,10 +7757,11 @@ bool VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
bool bUseSpatialIndex)
{
const FVector Pos(WorldX, WorldY, WorldZ);
const FCachedTunnel* TunnelData = Cache.Tunnels.GetData();
auto TestTunnel = [&](int32 TunnelIdx) -> bool
{
const FCachedTunnel& Tunnel = Cache.Tunnels[TunnelIdx];
const FCachedTunnel& Tunnel = TunnelData[TunnelIdx];
const bool bHasWorldChain = Tunnel.WorldControlPoints.Num() >= 2
&& Tunnel.WorldControlRadii.Num() == Tunnel.WorldControlPoints.Num();
const FVector& CenterlineMin = bHasWorldChain
@@ -7789,9 +7816,10 @@ bool VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
int32 CandidateCount = 0;
auto TestRange = [&](int32 Begin, int32 End)
{
const int32* ItemIndexData = Cache.TunnelSpatialIndex.ItemIndices.GetData();
for (int32 Cursor = Begin; Cursor < End; ++Cursor)
{
const int32 TunnelIdx = Cache.TunnelSpatialIndex.ItemIndices[Cursor];
const int32 TunnelIdx = ItemIndexData[Cursor];
if (!Cache.Tunnels.IsValidIndex(TunnelIdx))
{
continue;
@@ -7858,10 +7886,11 @@ void VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
}
const FVector ColumnPosition(WorldX, WorldY, 0.0f);
const FCachedTunnel* TunnelData = Cache.Tunnels.GetData();
auto EvaluateTunnel = [&](int32 TunnelIdx)
{
const FCachedTunnel& Tunnel = Cache.Tunnels[TunnelIdx];
const FCachedTunnel& Tunnel = TunnelData[TunnelIdx];
const FVFRoomFloorOwnership RoomMouthOwnership =
VF_FindWorldTunnelMouthOwnership(ColumnPosition, Tunnel, Cache.SDFBlendRadius);
float FloorZ = 0.0f;
@@ -858,7 +858,7 @@ namespace VoxelDensityProfile
FScopeToken BeginScope(EBucket Bucket)
{
FScopeToken Token;
FScopeToken Token{};
if (!IsEnabledFast())
{
return Token;
@@ -972,15 +972,4 @@ namespace VoxelDensityProfile
GSampledScopeActive = Token.bPreviousSampledScopeActive;
}
FScopedTimer::~FScopedTimer()
{
End();
}
void FScopedTimer::End()
{
if (!Token.bEntered) { return; }
EndScope(Bucket, Token);
Token.bEntered = false;
}
}
+15 -9
View File
@@ -206,8 +206,12 @@ private:
// uphill). See SurfaceDensityFromColumn for the warped-terrain union that makes the shelf.
struct FSurfaceColumn
{
float TerrainZ = 0.0f; float CeilSurf = 0.0f;
float OverhangAmp = 0.0f; float DirX = 0.0f; float DirY = 0.0f;
// Toutes les sorties sont écrites par ComputeSurfaceColumn avant Computed=true ; les valeurs
// par défaut ne sont donc jamais lues et leur constructeur implicite coûte dans le TLS.
// Every output is written by ComputeSurfaceColumn before Computed=true; the defaults are never
// read, and removing them keeps FSurfaceColumn trivial so TLS construction has no leaf work.
float TerrainZ, CeilSurf;
float OverhangAmp, DirX, DirY;
};
struct FSurfaceColumnBox
@@ -2355,7 +2359,6 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
Result = GetMazeDensity(WorldX, WorldY, WorldZ, CP_Maze); break;
case ECaveGeneratorType::SurfaceWorld:
{
FSurfaceColumn Col;
// Integer XY (the density grid) → reuse the column down its whole Z extent (T1.a).
// Fractional XY (gradient-normal samples) → compute directly (no cache key).
if (WorldX == FMath::FloorToFloat(WorldX) && WorldY == FMath::FloorToFloat(WorldY))
@@ -2373,17 +2376,20 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
Box.Cols[CI].OverhangAmp, Box.Cols[CI].DirX, Box.Cols[CI].DirY);
Box.Computed[CI] = true;
}
Col = Box.Cols[CI];
const FSurfaceColumn& Col = Box.Cols[CI];
Result = SurfaceDensityFromColumn(WorldX, WorldY, WorldZ,
Col.TerrainZ, Col.CeilSurf, Col.OverhangAmp,
Col.DirX, Col.DirY, CP_Surface);
}
else
{
float TerrainZ, CeilSurf, OverhangAmp, DirX, DirY;
ComputeSurfaceColumn(WorldX, WorldY, ChunkCoord.Z, CP_Surface, CP_BiomeCtx,
CP_SurfaceBiomeParams, CP_BiomeCache, Col.TerrainZ, Col.CeilSurf,
Col.OverhangAmp, Col.DirX, Col.DirY);
CP_SurfaceBiomeParams, CP_BiomeCache, TerrainZ, CeilSurf,
OverhangAmp, DirX, DirY);
Result = SurfaceDensityFromColumn(WorldX, WorldY, WorldZ,
TerrainZ, CeilSurf, OverhangAmp, DirX, DirY, CP_Surface);
}
Result = SurfaceDensityFromColumn(WorldX, WorldY, WorldZ,
Col.TerrainZ, Col.CeilSurf, Col.OverhangAmp, Col.DirX, Col.DirY, CP_Surface);
break;
}
case ECaveGeneratorType::VerticalShafts:
+288 -60
View File
@@ -12,6 +12,7 @@
#include "VoxelStartupTrace.h"
#include "VoxelTerrainOpDefinition.h" // For UVoxelTerrainOpDefinition::ApplyTo
#include "VoxelBiomeDefinition.h" // For UVoxelBiomeDefinition (biome context flatten)
#include "HAL/IConsoleManager.h"
#include "Misc/CommandLine.h"
#include "Misc/Crc.h"
#include "Misc/Parse.h"
@@ -23,6 +24,15 @@ namespace
{
std::atomic<uint64> GNextStrateManagerLifetimeId { 0 };
// Exact A/B switch for the repeated passage-floor projection item. The other field-
// preserving rewrites in this round are deliberately unconditional; this switch isolates
// the projection reuse without changing any density arithmetic.
int32 GVoxelForgeFloorRound1PassageProjectionCache = 1;
FAutoConsoleVariableRef CVarVoxelForgeFloorRound1PassageProjectionCache(
TEXT("voxel.FloorRound1PassageProjectionCache"),
GVoxelForgeFloorRound1PassageProjectionCache,
TEXT("Reuse exact passage floor projections within one density sample; 0 disables the A/B cache."));
struct FRuntimeRoughnessOverrides
{
bool bSurfaceRoughness = false;
@@ -185,6 +195,43 @@ static float PassageFBM(float X, float Seed)
namespace
{
// One projection result per passage is enough for the current voxel sample. The surrounding
// passage cache already moves with (manager, layout, chunk); a generation invalidates old flags
// before any caller can observe them. Keeping the entries indexed by passage avoids the TMap
// rehash path that this hot loop used to carry, while a collision-free hit remains exact.
struct FPassageFloorProjectionCacheEntry
{
// The sample key lives once in FPassageEvaluationCache. A monotonically increasing
// generation makes an entry's old flags inert without clearing its float payload.
// The payload is only read after the corresponding valid flag is set.
uint64 SampleGeneration = 0;
uint8 ComputedFlags = 0;
bool bNativeValid = false;
float NativeFloorZ = 0.0f;
float NativeSupportRadius = 0.0f;
bool bGenericValid = false;
float GenericFloorZ = 0.0f;
float GenericSupportRadius = 0.0f;
bool bWalkableAir = false;
enum : uint8
{
NativeComputed = 1u << 0,
GenericComputed = 1u << 1,
WalkableAirComputed = 1u << 2
};
FORCEINLINE bool Has(uint8 Flag) const
{
return (ComputedFlags & Flag) != 0;
}
FORCEINLINE void SetComputed(uint8 Flag)
{
ComputedFlags |= Flag;
}
};
/**
* One shared passage cache for the tube SDF and landing-floor fill.
*
@@ -199,6 +246,12 @@ namespace
FIntVector Chunk = FIntVector(INT32_MAX, INT32_MAX, INT32_MAX);
uint32 Version = 0xFFFFFFFFu;
TArray<int32> Nearby;
TArray<FPassageFloorProjectionCacheEntry> FloorProjections;
bool bFloorProjectionSampleValid = false;
double FloorProjectionX = 0.0;
double FloorProjectionY = 0.0;
double FloorProjectionZ = 0.0;
uint64 FloorProjectionGeneration = 0;
};
FPassageEvaluationCache& VF_GetPassageEvaluationCache()
@@ -225,6 +278,8 @@ namespace
Cache.Chunk = ChunkCoord;
Cache.Version = Version;
Cache.Nearby.Reset();
Cache.FloorProjections.Reset();
Cache.bFloorProjectionSampleValid = false;
if (!Manager) return Cache.Nearby;
const FVector ChunkCenter(
@@ -233,6 +288,7 @@ namespace
(ChunkCoord.Z + 0.5f) * (float)CHUNK_SIZE);
const float ChunkRadius = (float)CHUNK_SIZE * 0.8660254f + 3.0f;
const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
Cache.FloorProjections.SetNum(Passages.Num());
for (int32 PassageIndex = 0; PassageIndex < Passages.Num(); ++PassageIndex)
{
const FVoxelPassage& Passage = Passages[PassageIndex];
@@ -245,6 +301,59 @@ namespace
return Cache.Nearby;
}
FPassageFloorProjectionCacheEntry* VF_GetPassageFloorProjectionCacheEntry(
const UVoxelStrateManager* Manager,
const FVector& Position,
int32 PassageIndex)
{
if (GVoxelForgeFloorRound1PassageProjectionCache == 0
|| Manager == nullptr || PassageIndex < 0)
{
return nullptr;
}
FPassageEvaluationCache& Cache = VF_GetPassageEvaluationCache();
if (Cache.Owner != Manager
|| Cache.OwnerLifetimeId != Manager->GetCacheLifetimeId()
|| Cache.Version != Manager->GetLayoutVersion()
|| PassageIndex >= Cache.FloorProjections.Num())
{
return nullptr;
}
if (!Cache.bFloorProjectionSampleValid
|| Cache.FloorProjectionX != Position.X
|| Cache.FloorProjectionY != Position.Y
|| Cache.FloorProjectionZ != Position.Z)
{
Cache.bFloorProjectionSampleValid = true;
Cache.FloorProjectionX = Position.X;
Cache.FloorProjectionY = Position.Y;
Cache.FloorProjectionZ = Position.Z;
++Cache.FloorProjectionGeneration;
if (Cache.FloorProjectionGeneration == 0)
{
// Keep generation zero reserved for never-initialized entries. This is
// unreachable in a practical worker lifetime, but preserves the invariant if a
// process survives long enough to wrap the counter.
for (FPassageFloorProjectionCacheEntry& Candidate : Cache.FloorProjections)
{
Candidate.SampleGeneration = 0;
}
++Cache.FloorProjectionGeneration;
}
}
FPassageFloorProjectionCacheEntry* Entry =
Cache.FloorProjections.GetData() + PassageIndex;
if (Entry->SampleGeneration != Cache.FloorProjectionGeneration)
{
Entry->SampleGeneration = Cache.FloorProjectionGeneration;
Entry->ComputedFlags = 0;
}
return Entry;
}
// Passage relief is a function of the immutable passage descriptor and the actual XY column.
// Keep the cache fixed-size and thread-local, like the morphology floor cache: a long export
// cannot retain one entry per tile, and collisions only lose a hit. Fractional normal
@@ -425,49 +534,58 @@ namespace
0.0f, VF_PassageMaxReliefScale);
}
static bool VF_ProjectNativePassageFloor(
static bool VF_ProjectNativePassageFloorUncached(
const FVoxelPassage& Passage, const FVector& Position,
float& OutFloorZ, float& OutSupportRadius,
const UVoxelStrateManager* Manager = nullptr,
int32 PassageIndex = INDEX_NONE)
{
const int32 ControlPointCount = Passage.ControlPoints.Num();
if (!Passage.bNativeFloorEnabled
|| Passage.ControlPoints.Num() < 2
|| Passage.ControlRadii.Num() != Passage.ControlPoints.Num()
|| Passage.NativeFloorProfileZ.Num() != Passage.ControlPoints.Num())
|| ControlPointCount < 2
|| Passage.ControlRadii.Num() != ControlPointCount
|| Passage.NativeFloorProfileZ.Num() != ControlPointCount)
{
return false;
}
const FVector* ControlPointData = Passage.ControlPoints.GetData();
const float* ControlRadiusData = Passage.ControlRadii.GetData();
const float* NativeFloorProfileData = Passage.NativeFloorProfileZ.GetData();
const double QueryX = static_cast<double>(static_cast<float>(Position.X));
const double QueryY = static_cast<double>(static_cast<float>(Position.Y));
float BestDistanceSquared = FLT_MAX;
int32 BestSegment = INDEX_NONE;
float BestT = 0.0f;
float BestSupportRadius = 0.0f;
for (int32 SegmentIndex = 0;
SegmentIndex + 1 < Passage.ControlPoints.Num();
SegmentIndex + 1 < ControlPointCount;
++SegmentIndex)
{
const FVector& A = Passage.ControlPoints[SegmentIndex];
const FVector& B = Passage.ControlPoints[SegmentIndex + 1];
const FVector2D Delta(
static_cast<float>(B.X - A.X),
static_cast<float>(B.Y - A.Y));
const float LengthSquared = Delta.SizeSquared();
const FVector& A = ControlPointData[SegmentIndex];
const FVector& B = ControlPointData[SegmentIndex + 1];
const double AXY_X = static_cast<double>(static_cast<float>(A.X));
const double AXY_Y = static_cast<double>(static_cast<float>(A.Y));
const double DeltaX = static_cast<double>(static_cast<float>(B.X - A.X));
const double DeltaY = static_cast<double>(static_cast<float>(B.Y - A.Y));
const float LengthSquared = static_cast<float>(
DeltaX * DeltaX + DeltaY * DeltaY);
if (!(LengthSquared > KINDA_SMALL_NUMBER))
{
continue;
}
const FVector2D QueryXY(
static_cast<float>(Position.X),
static_cast<float>(Position.Y));
const FVector2D AXY(
static_cast<float>(A.X), static_cast<float>(A.Y));
const double Dot = (QueryX - AXY_X) * DeltaX
+ (QueryY - AXY_Y) * DeltaY;
const float T = FMath::Clamp(
FVector2D::DotProduct(QueryXY - AXY, Delta) / LengthSquared,
Dot / LengthSquared,
0.0f, 1.0f);
const FVector2D ClosestXY = AXY + Delta * T;
const float DistanceSquared = (QueryXY - ClosestXY).SizeSquared();
const double ClosestX = AXY_X + DeltaX * T;
const double ClosestY = AXY_Y + DeltaY * T;
const double DistanceX = QueryX - ClosestX;
const double DistanceY = QueryY - ClosestY;
const float DistanceSquared = static_cast<float>(
DistanceX * DistanceX + DistanceY * DistanceY);
if (DistanceSquared >= BestDistanceSquared)
{
continue;
@@ -478,8 +596,8 @@ namespace
BestT = T;
BestSupportRadius = FMath::Max(
FMath::Min(
FMath::Abs(Passage.ControlRadii[SegmentIndex]),
FMath::Abs(Passage.ControlRadii[SegmentIndex + 1])) - 0.5f,
FMath::Abs(ControlRadiusData[SegmentIndex]),
FMath::Abs(ControlRadiusData[SegmentIndex + 1])) - 0.5f,
VoxelPassageGeometry::PlayerRadiusVoxels);
}
@@ -491,21 +609,25 @@ namespace
const float ClampedT = FMath::Clamp(BestT, 0.0f, 1.0f);
OutFloorZ = FMath::Lerp(
Passage.NativeFloorProfileZ[BestSegment],
Passage.NativeFloorProfileZ[BestSegment + 1],
NativeFloorProfileData[BestSegment],
NativeFloorProfileData[BestSegment + 1],
ClampedT);
float ReliefScale = 0.0f;
if (Passage.NativeFloorReliefScales.IsValidIndex(BestSegment))
const int32 ReliefScaleCount = Passage.NativeFloorReliefScales.Num();
if (BestSegment >= 0 && BestSegment < ReliefScaleCount)
{
ReliefScale = Passage.NativeFloorReliefScales[BestSegment];
ReliefScale = Passage.NativeFloorReliefScales.GetData()[BestSegment];
}
float Envelope = 1.0f;
const float HorizontalRun = FVector2D(
static_cast<float>(Passage.ControlPoints[BestSegment + 1].X
- Passage.ControlPoints[BestSegment].X),
static_cast<float>(Passage.ControlPoints[BestSegment + 1].Y
- Passage.ControlPoints[BestSegment].Y)).Size();
const FVector& BestA = ControlPointData[BestSegment];
const FVector& BestB = ControlPointData[BestSegment + 1];
const double BestDeltaX = static_cast<double>(static_cast<float>(
BestB.X - BestA.X));
const double BestDeltaY = static_cast<double>(static_cast<float>(
BestB.Y - BestA.Y));
const float HorizontalRun = static_cast<float>(FMath::Sqrt(
BestDeltaX * BestDeltaX + BestDeltaY * BestDeltaY));
if (BestSegment == 0)
{
Envelope *= SmoothStep01(FMath::Clamp(
@@ -513,7 +635,7 @@ namespace
/ VoxelPassageGeometry::WalkableTunnelLandingApronVoxels,
0.0f, 1.0f));
}
if (BestSegment + 1 == Passage.ControlPoints.Num() - 1)
if (BestSegment + 1 == ControlPointCount - 1)
{
Envelope *= SmoothStep01(FMath::Clamp(
((1.0f - ClampedT) * HorizontalRun)
@@ -531,25 +653,82 @@ namespace
&& OutSupportRadius > 0.0f;
}
static bool VF_ProjectNativePassageFloor(
const FVoxelPassage& Passage, const FVector& Position,
float& OutFloorZ, float& OutSupportRadius,
const UVoxelStrateManager* Manager = nullptr,
int32 PassageIndex = INDEX_NONE)
{
FPassageFloorProjectionCacheEntry* Entry =
VF_GetPassageFloorProjectionCacheEntry(Manager, Position, PassageIndex);
if (Entry == nullptr)
{
return VF_ProjectNativePassageFloorUncached(
Passage, Position, OutFloorZ, OutSupportRadius,
Manager, PassageIndex);
}
if (!Entry->Has(FPassageFloorProjectionCacheEntry::NativeComputed))
{
Entry->bNativeValid = VF_ProjectNativePassageFloorUncached(
Passage, Position, Entry->NativeFloorZ,
Entry->NativeSupportRadius, Manager, PassageIndex);
Entry->SetComputed(FPassageFloorProjectionCacheEntry::NativeComputed);
}
if (Entry->bNativeValid)
{
OutFloorZ = Entry->NativeFloorZ;
OutSupportRadius = Entry->NativeSupportRadius;
}
return Entry->bNativeValid;
}
static bool VF_ProjectPassageFloor(
const FVoxelPassage& Passage, const FVector& Position,
float& OutFloorZ, float& OutSupportRadius,
const UVoxelStrateManager* Manager = nullptr,
int32 PassageIndex = INDEX_NONE)
{
if (VF_ProjectNativePassageFloor(
Passage, Position, OutFloorZ, OutSupportRadius,
Manager, PassageIndex))
FPassageFloorProjectionCacheEntry* Entry =
VF_GetPassageFloorProjectionCacheEntry(Manager, Position, PassageIndex);
if (Entry != nullptr
&& Entry->Has(FPassageFloorProjectionCacheEntry::GenericComputed))
{
return true;
if (Entry->bGenericValid)
{
OutFloorZ = Entry->GenericFloorZ;
OutSupportRadius = Entry->GenericSupportRadius;
}
return Entry->bGenericValid;
}
if (!Passage.bWalkableTunnelContract)
float FloorZ = 0.0f;
float SupportRadius = 0.0f;
bool bValid = VF_ProjectNativePassageFloor(
Passage, Position, FloorZ, SupportRadius,
Manager, PassageIndex);
if (!bValid && Passage.bWalkableTunnelContract)
{
return false;
bValid = VoxelPassageGeometry::ProjectWalkableTunnelFloor(
Passage.ControlPoints, Passage.ControlRadii, Position,
FloorZ, SupportRadius);
}
return VoxelPassageGeometry::ProjectWalkableTunnelFloor(
Passage.ControlPoints, Passage.ControlRadii, Position,
OutFloorZ, OutSupportRadius);
if (Entry != nullptr)
{
Entry->bGenericValid = bValid;
if (bValid)
{
Entry->GenericFloorZ = FloorZ;
Entry->GenericSupportRadius = SupportRadius;
}
Entry->SetComputed(FPassageFloorProjectionCacheEntry::GenericComputed);
}
if (bValid)
{
OutFloorZ = FloorZ;
OutSupportRadius = SupportRadius;
}
return bValid;
}
}
@@ -700,7 +879,9 @@ static bool VF_IsWalkableTunnelAir(
const FVoxelPassage& Passage,
const FVector& Position,
const UVoxelStrateManager* Manager = nullptr,
int32 PassageIndex = INDEX_NONE)
int32 PassageIndex = INDEX_NONE,
float* OutFloorZ = nullptr,
float* OutSupportRadius = nullptr)
{
if (!Passage.bWalkableTunnelContract
|| Passage.ControlPoints.Num() < 2
@@ -709,6 +890,25 @@ static bool VF_IsWalkableTunnelAir(
return false;
}
FPassageFloorProjectionCacheEntry* Entry =
VF_GetPassageFloorProjectionCacheEntry(Manager, Position, PassageIndex);
if (Entry != nullptr
&& Entry->Has(FPassageFloorProjectionCacheEntry::WalkableAirComputed))
{
if (Entry->bWalkableAir)
{
if (OutFloorZ != nullptr)
{
*OutFloorZ = Entry->GenericFloorZ;
}
if (OutSupportRadius != nullptr)
{
*OutSupportRadius = Entry->GenericSupportRadius;
}
}
return Entry->bWalkableAir;
}
float FloorZ = 0.0f;
float SupportRadius = 0.0f;
if (!VF_ProjectPassageFloor(
@@ -722,9 +922,26 @@ static bool VF_IsWalkableTunnelAir(
// stable air volume even when a source archetype or a disturbance would otherwise refill the
// shallow part of the rounded SDF. The floor writer below owns the closed lower face.
const float AirHeight = 2.0f * SupportRadius;
return Position.Z > FloorZ
const bool bWalkableAir = Position.Z > FloorZ
+ VoxelPassageGeometry::WalkableTunnelFloorAirClearanceVoxels
&& Position.Z < FloorZ + AirHeight - KINDA_SMALL_NUMBER;
if (Entry != nullptr)
{
Entry->bWalkableAir = bWalkableAir;
Entry->SetComputed(FPassageFloorProjectionCacheEntry::WalkableAirComputed);
}
if (bWalkableAir)
{
if (OutFloorZ != nullptr)
{
*OutFloorZ = FloorZ;
}
if (OutSupportRadius != nullptr)
{
*OutSupportRadius = SupportRadius;
}
}
return bWalkableAir;
}
static bool VF_FindWalkableTunnelAir(
@@ -748,21 +965,21 @@ static bool VF_FindWalkableTunnelAir(
FMath::FloorToInt(Position.Z / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(Manager, ChunkCoord);
const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
const FVoxelPassage* PassageData = Passages.GetData();
for (const int32 PassageIndex : Nearby)
{
if (!Passages.IsValidIndex(PassageIndex))
{
continue;
}
const FVoxelPassage& Passage = Passages[PassageIndex];
const FVoxelPassage& Passage = PassageData[PassageIndex];
if (FVector::DistSquared(Position, Passage.BoundCenter) > Passage.BoundRadiusSq
|| !VF_IsWalkableTunnelAir(Passage, Position, Manager, PassageIndex))
|| !VF_IsWalkableTunnelAir(
Passage, Position, Manager, PassageIndex,
&OutFloorZ, &OutSupportRadius))
{
continue;
}
VF_ProjectPassageFloor(
Passage, Position, OutFloorZ, OutSupportRadius,
Manager, PassageIndex);
OutPassageIndex = PassageIndex;
return true;
}
@@ -783,13 +1000,14 @@ static bool VF_IsAnyPassageFloorAt(
FMath::FloorToInt(Position.Z / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(Manager, ChunkCoord);
const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
const FVoxelPassage* PassageData = Passages.GetData();
for (const int32 PassageIndex : Nearby)
{
if (!Passages.IsValidIndex(PassageIndex))
{
continue;
}
const FVoxelPassage& Passage = Passages[PassageIndex];
const FVoxelPassage& Passage = PassageData[PassageIndex];
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
@@ -2242,6 +2460,7 @@ float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float
float MinSDF = FLT_MAX;
const float BlendK = 3.0f; // Smooth blend for passage junctions
const FVoxelPassage* PassageData = Passages.GetData();
//=========================================================================
// PASSAGES — tapered capsule chains between strates (per-strate PassageConfig).
@@ -2251,7 +2470,7 @@ float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float
const FVector Pos(WorldX, WorldY, WorldZ);
for (int32 PIdx : Nearby)
{
const FVoxelPassage& P = Passages[PIdx];
const FVoxelPassage& P = PassageData[PIdx];
// BOUNDING-SPHERE REJECT: skip passages this voxel can't possibly be inside.
// EvaluateModifierSDF runs PER VOXEL and used to evaluate every passage's full
// capsule chain unconditionally — the dominant lag source once passages became
@@ -2263,19 +2482,22 @@ float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float
VoxelDensityProfile::ECounter::PassageEvaluated);
}
if (P.ControlPoints.Num() >= 2)
const int32 ControlPointCount = P.ControlPoints.Num();
if (ControlPointCount >= 2)
{
// Tapered capsule chain along the control points. ControlRadii (if present)
// gives the per-point width so the tunnel can flare at the mouths and pinch
// in the middle; otherwise the uniform Radius is used.
const bool bTaper = (P.ControlRadii.Num() == P.ControlPoints.Num());
const FVector* ControlPointData = P.ControlPoints.GetData();
const float* ControlRadiusData = P.ControlRadii.GetData();
float PassageSDF = FLT_MAX;
for (int32 j = 0; j < P.ControlPoints.Num() - 1; j++)
for (int32 j = 0; j < ControlPointCount - 1; j++)
{
const float rA = bTaper ? P.ControlRadii[j] : P.Radius;
const float rB = bTaper ? P.ControlRadii[j + 1] : P.Radius;
const float rA = bTaper ? ControlRadiusData[j] : P.Radius;
const float rB = bTaper ? ControlRadiusData[j + 1] : P.Radius;
const float SegSDF = VoxelSDF::TaperedCapsule(
Pos, P.ControlPoints[j], P.ControlPoints[j + 1], rA, rB);
Pos, ControlPointData[j], ControlPointData[j + 1], rA, rB);
PassageSDF = VoxelSDF::SmoothMin(PassageSDF, SegSDF, BlendK);
}
@@ -2334,10 +2556,11 @@ void UVoxelStrateManager::ApplyPassageModifier(
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ);
for (const int32 PassageIndex : Nearby)
{
const FVoxelPassage& Passage = Passages[PassageIndex];
const FVoxelPassage& Passage = PassageData[PassageIndex];
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
@@ -2386,6 +2609,7 @@ void UVoxelStrateManager::ApplyPassageLandingAir(
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
if (Nearby.Num() == 0) return;
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ);
// A smooth room blend may overlap the end of a ramp. The analytic support plane owns that
// overlap; otherwise landing air can erase the tunnel's final-density floor after the tunnel
@@ -2397,7 +2621,7 @@ void UVoxelStrateManager::ApplyPassageLandingAir(
float MinLandingSDF = FLT_MAX;
for (const int32 PassageIndex : Nearby)
{
const FVoxelPassage& Passage = Passages[PassageIndex];
const FVoxelPassage& Passage = PassageData[PassageIndex];
if (FVector::DistSquared(Position, Passage.BoundCenter) > Passage.BoundRadiusSq)
{
continue;
@@ -2477,10 +2701,11 @@ void UVoxelStrateManager::ApplyPassageLandingFloorMC(
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ);
for (const int32 PassageIndex : Nearby)
{
const FVoxelPassage& Passage = Passages[PassageIndex];
const FVoxelPassage& Passage = PassageData[PassageIndex];
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
@@ -2529,6 +2754,7 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
if (Nearby.Num() == 0) return;
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ);
const bool bSuppressFloor =
VoxelPassageGeometry::VerticalShaftConnectorAirMarker();
@@ -2544,7 +2770,7 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
for (const int32 PassageIndex : Nearby)
{
if (!Passages.IsValidIndex(PassageIndex)) continue;
const FVoxelPassage& Passage = Passages[PassageIndex];
const FVoxelPassage& Passage = PassageData[PassageIndex];
const bool bTunnelSupportFloor =
bPassageSupportFloorWritesEnabled
@@ -2640,6 +2866,7 @@ void UVoxelStrateManager::ApplyPassageNativeFloorMC(
return;
}
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ);
for (const int32 PassageIndex : Nearby)
{
@@ -2647,7 +2874,7 @@ void UVoxelStrateManager::ApplyPassageNativeFloorMC(
{
continue;
}
const FVoxelPassage& Passage = Passages[PassageIndex];
const FVoxelPassage& Passage = PassageData[PassageIndex];
if (!Passage.bNativeFloorEnabled)
{
continue;
@@ -2689,10 +2916,11 @@ void UVoxelStrateManager::ApplyPassageLandingRoomFloorMC(
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ);
for (const int32 PassageIndex : Nearby)
{
const FVoxelPassage& Passage = Passages[PassageIndex];
const FVoxelPassage& Passage = PassageData[PassageIndex];
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
&& (VF_IsPassageRoomFloor(Position, Passage.UpperLanding)
|| VF_IsPassageRoomFloor(Position, Passage.LowerLanding)))
+25 -11
View File
@@ -383,12 +383,12 @@ namespace VoxelDensityProfile
struct FScopeToken
{
bool bEntered = false;
bool bSampled = false;
bool bBlockTiming = false;
bool bPreviousSampled = false;
bool bPreviousSampledScopeActive = false;
uint64 StartCycles = 0;
bool bEntered;
bool bSampled;
bool bBlockTiming;
bool bPreviousSampled;
bool bPreviousSampledScopeActive;
uint64 StartCycles;
};
VOXELFORGE_API FScopeToken BeginScope(EBucket Bucket);
@@ -398,16 +398,16 @@ namespace VoxelDensityProfile
{
public:
explicit FScopedTimer(EBucket InBucket)
: Bucket(InBucket)
{
if (IsEnabledFast() && ShouldProbeScopeFast(InBucket))
{
Bucket = InBucket;
Token = BeginScope(InBucket);
bActive = Token.bEntered;
}
}
explicit FScopedTimer(const TCHAR* InName)
: Bucket(EBucket::OtherOp)
{
if (IsEnabledFast())
{
@@ -415,21 +415,35 @@ namespace VoxelDensityProfile
if (ShouldProbeScopeFast(Bucket))
{
Token = BeginScope(Bucket);
bActive = Token.bEntered;
}
}
}
~FScopedTimer();
FORCEINLINE ~FScopedTimer()
{
End();
}
// End a range before its lexical scope ends. This lets GetDensityAt split its enclosing
// total into disjoint prologue/core/post phases without moving the large cache block.
void End();
FORCEINLINE void End()
{
if (!bActive)
{
return;
}
EndScope(Bucket, Token);
Token.bEntered = false;
bActive = false;
}
FScopedTimer(const FScopedTimer&) = delete;
FScopedTimer& operator=(const FScopedTimer&) = delete;
private:
EBucket Bucket = EBucket::OtherOp;
EBucket Bucket;
FScopeToken Token;
bool bActive = false;
};
}
+25 -12
View File
@@ -302,35 +302,48 @@ namespace VoxelPassageGeometry
return false;
}
const FVector2D QueryXY(Position.X, Position.Y);
const double QueryX = Position.X;
const double QueryY = Position.Y;
const int32 ControlPointCount = ControlPoints.Num();
const FVector* ControlPointData = ControlPoints.GetData();
const float* ControlRadiusData = ControlRadii.GetData();
float BestDistanceSquared = FLT_MAX;
bool bFoundSegment = false;
for (int32 SegmentIndex = 0;
SegmentIndex + 1 < ControlPoints.Num();
SegmentIndex + 1 < ControlPointCount;
++SegmentIndex)
{
const FVector& A = ControlPoints[SegmentIndex];
const FVector& B = ControlPoints[SegmentIndex + 1];
const FVector2D AXY(A.X, A.Y);
const FVector2D Delta = FVector2D(B.X, B.Y) - AXY;
const float LengthSquared = Delta.SizeSquared();
const FVector& A = ControlPointData[SegmentIndex];
const FVector& B = ControlPointData[SegmentIndex + 1];
const double AXY_X = A.X;
const double AXY_Y = A.Y;
const double DeltaX = B.X - AXY_X;
const double DeltaY = B.Y - AXY_Y;
const float LengthSquared = static_cast<float>(
DeltaX * DeltaX + DeltaY * DeltaY);
if (LengthSquared <= KINDA_SMALL_NUMBER)
{
continue;
}
const double Dot = (QueryX - AXY_X) * DeltaX
+ (QueryY - AXY_Y) * DeltaY;
const float T = FMath::Clamp(
FVector2D::DotProduct(QueryXY - AXY, Delta) / LengthSquared,
Dot / LengthSquared,
0.0f, 1.0f);
const FVector2D ClosestXY = AXY + Delta * T;
const float DistanceSquared = (QueryXY - ClosestXY).SizeSquared();
const double ClosestX = AXY_X + DeltaX * T;
const double ClosestY = AXY_Y + DeltaY * T;
const double DistanceX = QueryX - ClosestX;
const double DistanceY = QueryY - ClosestY;
const float DistanceSquared = static_cast<float>(
DistanceX * DistanceX + DistanceY * DistanceY);
if (DistanceSquared >= BestDistanceSquared)
{
continue;
}
const float StartRadius = FMath::Abs(ControlRadii[SegmentIndex]);
const float EndRadius = FMath::Abs(ControlRadii[SegmentIndex + 1]);
const float StartRadius = FMath::Abs(ControlRadiusData[SegmentIndex]);
const float EndRadius = FMath::Abs(ControlRadiusData[SegmentIndex + 1]);
OutFloorZ = FMath::Lerp(
TunnelFloorZ(A, StartRadius),
TunnelFloorZ(B, EndRadius),