measure(step2b): per-brick setup rebuilds the chunk caches -- 10x slower; live cost attributed
Editor-only brick API (<= 8^3 points per call, TunnelNetwork/Underwater op-stack routes, 1.16% scalar fallback). Exact: 0 Q16/sign disagreements on the full 259^3 ring and the five-seed probe set, same hash on 1 and 6 workers, reach shrink still caught (18,107 Q16 / 735 sign). Speed: 69.19 s wall, 17.6 us/sample vs live 7.04 s, 1.65 us/sample. Stack sampler (inclusive): BuildChunkCache 93.5%, PropagateSdfOverBox 92.5%, BuildActiveOpList 92.6% -- each brick rebuilds the cache windows and active-op lists; EvalBlock itself is 3.6%. Live path attribution (inclusive, 68,934 stacks): op-stack RoomGraph EvalBlock 54.4%, SDF and spatial candidate scans ~28%, tunnel core 18.4%, passage floor segment 15.0%, structural posts 14.6%, noise octaves 2.6%, disturbances 0.6%, diff layer 0.3%. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
41a9265ebd
commit
fd9f809c1c
7 files changed
+749
-97
No files matched your search
@@ -641,6 +641,8 @@ thread_local int32 VoxelGenLOD::OctaveBias = 0;
|
||||
struct FVoxelDensityBlockSession
|
||||
{
|
||||
bool bActive = false;
|
||||
bool bBatchRequested = false;
|
||||
bool bBatchSucceeded = false;
|
||||
FIntVector Origin = FIntVector::ZeroValue;
|
||||
int32 Step = 1;
|
||||
int32 SizeX = 0;
|
||||
@@ -650,6 +652,7 @@ struct FVoxelDensityBlockSession
|
||||
TArray<uint8> Valid;
|
||||
TArray<FVoxelOpSample> BlockScratch;
|
||||
TArray<int32> ActiveOps;
|
||||
TArray<float> BatchResults;
|
||||
|
||||
void Begin(FIntVector InOrigin, int32 InStep, int32 InSizeX, int32 InSizeY, int32 InSizeZ)
|
||||
{
|
||||
@@ -665,6 +668,9 @@ struct FVoxelDensityBlockSession
|
||||
Valid.Reset();
|
||||
BlockScratch.Reset();
|
||||
ActiveOps.Reset();
|
||||
BatchResults.Reset();
|
||||
bBatchRequested = false;
|
||||
bBatchSucceeded = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -672,6 +678,9 @@ struct FVoxelDensityBlockSession
|
||||
// (gaps, out-of-layout, degenerate strates) use the ordinary scalar path and should not
|
||||
// pay for a block scratch lattice merely because the mesher opened the scoped hand-off.
|
||||
BlockScratch.Reset();
|
||||
BatchResults.Reset();
|
||||
bBatchRequested = false;
|
||||
bBatchSucceeded = false;
|
||||
}
|
||||
|
||||
void End()
|
||||
@@ -683,6 +692,9 @@ struct FVoxelDensityBlockSession
|
||||
Valid.Reset();
|
||||
BlockScratch.Reset();
|
||||
ActiveOps.Reset();
|
||||
BatchResults.Reset();
|
||||
bBatchRequested = false;
|
||||
bBatchSucceeded = false;
|
||||
}
|
||||
|
||||
int32 Index(int32 X, int32 Y, int32 Z) const
|
||||
@@ -2709,6 +2721,77 @@ void UVoxelGenerator::EndDensityBlock() const
|
||||
GVoxelDensityBlockSession.End();
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
bool UVoxelGenerator::EvaluateDensityBrick(
|
||||
FIntVector OriginVoxels, int32 SizeX, int32 SizeY, int32 SizeZ,
|
||||
TArray<float>& OutDensities, int32& OutFallbackSamples) const
|
||||
{
|
||||
OutDensities.Reset();
|
||||
OutFallbackSamples = 0;
|
||||
const int64 SampleCount64 = static_cast<int64>(SizeX) * SizeY * SizeZ;
|
||||
if (SizeX <= 0 || SizeY <= 0 || SizeZ <= 0
|
||||
|| SizeX > 8 || SizeY > 8 || SizeZ > 8 || SampleCount64 > 512)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FIntVector Last = OriginVoxels + FIntVector(SizeX - 1, SizeY - 1, SizeZ - 1);
|
||||
const auto SameCell = [](int32 A, int32 B, int32 CellSize)
|
||||
{
|
||||
return FMath::FloorToInt(static_cast<float>(A) / static_cast<float>(CellSize))
|
||||
== FMath::FloorToInt(static_cast<float>(B) / static_cast<float>(CellSize));
|
||||
};
|
||||
if (!SameCell(OriginVoxels.X, Last.X, 8)
|
||||
|| !SameCell(OriginVoxels.Y, Last.Y, 8)
|
||||
|| !SameCell(OriginVoxels.Z, Last.Z, 8)
|
||||
|| !SameCell(OriginVoxels.X, Last.X, CHUNK_SIZE)
|
||||
|| !SameCell(OriginVoxels.Y, Last.Y, CHUNK_SIZE)
|
||||
|| !SameCell(OriginVoxels.Z, Last.Z, CHUNK_SIZE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32 SampleCount = static_cast<int32>(SampleCount64);
|
||||
BeginDensityBlock(OriginVoxels, 1, SizeX, SizeY, SizeZ);
|
||||
GVoxelDensityBlockSession.bBatchRequested = true;
|
||||
GVoxelDensityBlockSession.bBatchSucceeded = false;
|
||||
GVoxelDensityBlockSession.BatchResults.SetNumUninitialized(SampleCount);
|
||||
|
||||
const float FirstDensity = GetDensityAt(
|
||||
static_cast<float>(OriginVoxels.X),
|
||||
static_cast<float>(OriginVoxels.Y),
|
||||
static_cast<float>(OriginVoxels.Z));
|
||||
if (GVoxelDensityBlockSession.bBatchSucceeded)
|
||||
{
|
||||
OutDensities.SetNumUninitialized(SampleCount);
|
||||
FMemory::Memcpy(
|
||||
OutDensities.GetData(), GVoxelDensityBlockSession.BatchResults.GetData(),
|
||||
static_cast<SIZE_T>(SampleCount) * sizeof(float));
|
||||
EndDensityBlock();
|
||||
return OutDensities.Num() == SampleCount;
|
||||
}
|
||||
|
||||
// Recipes without the editor brick port retain the canonical point evaluator. The first
|
||||
// call already produced point zero; the remaining samples are explicit counted fallbacks.
|
||||
GVoxelDensityBlockSession.bBatchRequested = false;
|
||||
OutDensities.SetNumUninitialized(SampleCount);
|
||||
OutDensities[0] = FirstDensity;
|
||||
for (int32 Index = 1; Index < SampleCount; ++Index)
|
||||
{
|
||||
const int32 X = Index % SizeX;
|
||||
const int32 Y = (Index / SizeX) % SizeY;
|
||||
const int32 Z = Index / (SizeX * SizeY);
|
||||
OutDensities[Index] = GetDensityAt(
|
||||
static_cast<float>(OriginVoxels.X + X),
|
||||
static_cast<float>(OriginVoxels.Y + Y),
|
||||
static_cast<float>(OriginVoxels.Z + Z));
|
||||
}
|
||||
OutFallbackSamples = SampleCount;
|
||||
EndDensityBlock();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
|
||||
float* OutPreEditDensity) const
|
||||
{
|
||||
@@ -2789,6 +2872,12 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
|
||||
bIsGapChunk = StrateManager->IsGapChunk(ChunkCoord);
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
bool bEvaluateDensityBatch = false;
|
||||
int32 BatchSampleCount = 0;
|
||||
int32 DensityBatchCount = 1;
|
||||
bool bBrickHasDiffMods = false;
|
||||
#endif
|
||||
if (StrateManager && bIsGapChunk)
|
||||
{
|
||||
// SOLID BEDROCK gap between two strates. The auto-carved passages still tunnel
|
||||
@@ -3361,6 +3450,160 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
|
||||
}
|
||||
DensityCoreTimer.End();
|
||||
|
||||
#if WITH_EDITOR
|
||||
// The editor region program asks this one routed sample to fill every point in the
|
||||
// already-evaluated operator block. Its bricks are contained by one 8-voxel research
|
||||
// bin and one 32-voxel chunk, so the stack, params, route, and cache key above are shared.
|
||||
BatchSampleCount = GVoxelDensityBlockSession.SizeX
|
||||
* GVoxelDensityBlockSession.SizeY * GVoxelDensityBlockSession.SizeZ;
|
||||
bEvaluateDensityBatch = GVoxelDensityBlockSession.bBatchRequested
|
||||
&& bUsedOpBlockSample && !bUsedFusedEvaluator && CP_UseOpStack
|
||||
&& !bIsGapChunk && StrateManager != nullptr
|
||||
&& (CP_GenType == ECaveGeneratorType::TunnelNetwork
|
||||
|| CP_GenType == ECaveGeneratorType::Underwater)
|
||||
&& GVoxelDensityBlockSession.Samples.Num() == BatchSampleCount
|
||||
&& GVoxelDensityBlockSession.BatchResults.Num() == BatchSampleCount;
|
||||
DensityBatchCount = bEvaluateDensityBatch ? BatchSampleCount : 1;
|
||||
bool bBrickHasDisturbanceCandidate = true;
|
||||
bool bBrickHasPassagePostCandidate = true;
|
||||
if (bEvaluateDensityBatch)
|
||||
{
|
||||
const FIntVector BrickLast = GVoxelDensityBlockSession.Origin + FIntVector(
|
||||
(GVoxelDensityBlockSession.SizeX - 1) * GVoxelDensityBlockSession.Step,
|
||||
(GVoxelDensityBlockSession.SizeY - 1) * GVoxelDensityBlockSession.Step,
|
||||
(GVoxelDensityBlockSession.SizeZ - 1) * GVoxelDensityBlockSession.Step);
|
||||
const FBox BrickBox(
|
||||
FVector(GVoxelDensityBlockSession.Origin.X,
|
||||
GVoxelDensityBlockSession.Origin.Y,
|
||||
GVoxelDensityBlockSession.Origin.Z),
|
||||
FVector(BrickLast.X, BrickLast.Y, BrickLast.Z));
|
||||
bBrickHasDisturbanceCandidate = !VoxelDensityAblation::IsDisturbancesOff()
|
||||
&& VF_AnyDisturbanceCanTouchBox(
|
||||
BrickBox, CP_Dist, static_cast<uint32>(Seed));
|
||||
|
||||
const bool bAllPassagePostsOff =
|
||||
VoxelDensityAblation::IsPassageStructuralPostsOff()
|
||||
&& VoxelDensityAblation::IsNativeFloorOff()
|
||||
&& VoxelDensityAblation::IsLandingPostsOff();
|
||||
if (bAllPassagePostsOff)
|
||||
{
|
||||
bBrickHasPassagePostCandidate = false;
|
||||
}
|
||||
else if (!VoxelDensityAblation::IsPassageCarvingOff())
|
||||
{
|
||||
bBrickHasPassagePostCandidate = StrateManager->AnyPassageNearBox(
|
||||
BrickBox.Min, BrickBox.Max);
|
||||
}
|
||||
bBrickHasDiffMods = DiffLayer != nullptr && DiffLayer->HasAnyMods();
|
||||
}
|
||||
const bool bApplyBrickDisturbances =
|
||||
!bEvaluateDensityBatch || bBrickHasDisturbanceCandidate;
|
||||
const bool bApplyBrickPassagePosts =
|
||||
!bEvaluateDensityBatch || bBrickHasPassagePostCandidate;
|
||||
#else
|
||||
const bool bApplyBrickDisturbances = true;
|
||||
const bool bApplyBrickPassagePosts = true;
|
||||
#endif
|
||||
// Chunk parameters and composer/custom recipe structure are fixed by the route above.
|
||||
// Resolve the landing writer's base once for the whole brick, including edge bricks.
|
||||
float BrickLandingBaseDensity = CP_Dist.BaseDensity;
|
||||
#if WITH_EDITOR
|
||||
if (CP_UseComposerRegions && CP_ComposerRegions.bHasGlobalStructuralParams)
|
||||
{
|
||||
BrickLandingBaseDensity = CP_ComposerRegions.BaseDensity;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (CP_UseCustomRecipe)
|
||||
{
|
||||
switch (CP_CustomRecipe.StructuralParamBlock)
|
||||
{
|
||||
case EVoxelStrateParamBlock::TunnelNetwork:
|
||||
BrickLandingBaseDensity = CP_CustomParams.TunnelNetworkParams.BaseDensity;
|
||||
break;
|
||||
case EVoxelStrateParamBlock::Slab:
|
||||
BrickLandingBaseDensity = CP_CustomParams.SlabParams.BaseDensity;
|
||||
break;
|
||||
case EVoxelStrateParamBlock::Maze:
|
||||
BrickLandingBaseDensity = CP_CustomParams.MazeParams.BaseDensity;
|
||||
break;
|
||||
case EVoxelStrateParamBlock::Surface:
|
||||
BrickLandingBaseDensity = CP_CustomParams.SurfaceParams.BaseDensity;
|
||||
break;
|
||||
case EVoxelStrateParamBlock::VerticalShaft:
|
||||
BrickLandingBaseDensity = CP_CustomParams.VerticalShaftParams.BaseDensity;
|
||||
break;
|
||||
case EVoxelStrateParamBlock::FloatingIsland:
|
||||
BrickLandingBaseDensity = CP_CustomParams.FloatingIslandParams.BaseDensity;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (CP_GenType)
|
||||
{
|
||||
case ECaveGeneratorType::FlatPlain:
|
||||
case ECaveGeneratorType::CrystalChamber:
|
||||
BrickLandingBaseDensity = CP_Slab.BaseDensity;
|
||||
break;
|
||||
case ECaveGeneratorType::Maze:
|
||||
BrickLandingBaseDensity = CP_Maze.BaseDensity;
|
||||
break;
|
||||
case ECaveGeneratorType::SurfaceWorld:
|
||||
BrickLandingBaseDensity = CP_Surface.BaseDensity;
|
||||
break;
|
||||
case ECaveGeneratorType::VerticalShafts:
|
||||
BrickLandingBaseDensity = CP_Vert.BaseDensity;
|
||||
break;
|
||||
case ECaveGeneratorType::FloatingIslands:
|
||||
BrickLandingBaseDensity = CP_Float.BaseDensity;
|
||||
break;
|
||||
case ECaveGeneratorType::TunnelNetwork:
|
||||
case ECaveGeneratorType::Underwater:
|
||||
default:
|
||||
BrickLandingBaseDensity = CP_Tunnel.BaseDensity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int32 DensityBatchIndex = 0;
|
||||
#if WITH_EDITOR
|
||||
DensityBatchIndex < DensityBatchCount;
|
||||
#else
|
||||
DensityBatchIndex < 1;
|
||||
#endif
|
||||
++DensityBatchIndex)
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
if (bEvaluateDensityBatch)
|
||||
{
|
||||
const int32 BatchX = DensityBatchIndex % GVoxelDensityBlockSession.SizeX;
|
||||
const int32 BatchY = (DensityBatchIndex / GVoxelDensityBlockSession.SizeX)
|
||||
% GVoxelDensityBlockSession.SizeY;
|
||||
const int32 BatchZ = DensityBatchIndex
|
||||
/ (GVoxelDensityBlockSession.SizeX * GVoxelDensityBlockSession.SizeY);
|
||||
WorldX = static_cast<float>(GVoxelDensityBlockSession.Origin.X
|
||||
+ BatchX * GVoxelDensityBlockSession.Step);
|
||||
WorldY = static_cast<float>(GVoxelDensityBlockSession.Origin.Y
|
||||
+ BatchY * GVoxelDensityBlockSession.Step);
|
||||
WorldZ = static_cast<float>(GVoxelDensityBlockSession.Origin.Z
|
||||
+ BatchZ * GVoxelDensityBlockSession.Step);
|
||||
|
||||
const FVoxelOpSample& BatchSample =
|
||||
GVoxelDensityBlockSession.Samples[DensityBatchIndex];
|
||||
Result = -BatchSample.Density;
|
||||
bUsedOpBlockSample = true;
|
||||
bBlockHasTunnelCore = BatchSample.bHasTunnelCoreWorldEvaluation;
|
||||
BlockTunnelCoreSDF = BatchSample.TunnelCoreWorldSDF;
|
||||
bBlockTunnelCoreRoomFloor = BatchSample.bTunnelCoreRoomFloor;
|
||||
bBlockHasTunnelCoreSweptFloor = BatchSample.bHasTunnelCoreSweptFloor;
|
||||
BlockTunnelCoreSweptFloorZ = BatchSample.TunnelCoreSweptFloorZ;
|
||||
BlockTunnelCoreSweptFloorRadius = BatchSample.TunnelCoreSweptFloorRadius;
|
||||
bUsedFusedEvaluator = false;
|
||||
bRoomOwnsBottomValid = false;
|
||||
}
|
||||
#endif
|
||||
// Resolve the graph's authored floor before the disturbance pass. The operator-stack
|
||||
// source has already computed this exact result while publishing its sample; native
|
||||
// tunnel generation uses the prepared immutable cache. Keeping the result here means the
|
||||
@@ -3420,8 +3663,11 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
|
||||
VoxelDensityProfile::EBucket::DensityDisturbances);
|
||||
const bool bProtectVerticalShaftAir =
|
||||
VoxelPassageGeometry::VerticalShaftConnectorAirMarker();
|
||||
ApplyDisturbances(Result, WorldX, WorldY, WorldZ, CP_Dist, (uint32)Seed,
|
||||
bProtectVerticalShaftAir);
|
||||
if (bApplyBrickDisturbances)
|
||||
{
|
||||
ApplyDisturbances(Result, WorldX, WorldY, WorldZ, CP_Dist, (uint32)Seed,
|
||||
bProtectVerticalShaftAir);
|
||||
}
|
||||
DensityDisturbancesTimer.End();
|
||||
|
||||
// A disturbance is allowed to add visual detail, but it must not refill the landing's
|
||||
@@ -3432,71 +3678,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
|
||||
VoxelDensityProfile::EBucket::DensityStructuralPosts);
|
||||
VoxelDensityProfile::FScopedTimer StructuralTailTimer(
|
||||
VoxelDensityProfile::EBucket::StructuralTail);
|
||||
float LandingBaseDensity = CP_Dist.BaseDensity;
|
||||
#if WITH_EDITOR
|
||||
// A composer parent can deliberately use a different structural base than the authored
|
||||
// definition. The landing floor was already applied by PassageCarveOp with that parent
|
||||
// base; use the same value for this post-disturbance MC backstop or live-vs-direct stack
|
||||
// evaluation would differ exactly on floor voxels.
|
||||
if (CP_UseComposerRegions && CP_ComposerRegions.bHasGlobalStructuralParams)
|
||||
{
|
||||
LandingBaseDensity = CP_ComposerRegions.BaseDensity;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (CP_UseCustomRecipe)
|
||||
{
|
||||
switch (CP_CustomRecipe.StructuralParamBlock)
|
||||
{
|
||||
case EVoxelStrateParamBlock::TunnelNetwork:
|
||||
LandingBaseDensity = CP_CustomParams.TunnelNetworkParams.BaseDensity;
|
||||
break;
|
||||
case EVoxelStrateParamBlock::Slab:
|
||||
LandingBaseDensity = CP_CustomParams.SlabParams.BaseDensity;
|
||||
break;
|
||||
case EVoxelStrateParamBlock::Maze:
|
||||
LandingBaseDensity = CP_CustomParams.MazeParams.BaseDensity;
|
||||
break;
|
||||
case EVoxelStrateParamBlock::Surface:
|
||||
LandingBaseDensity = CP_CustomParams.SurfaceParams.BaseDensity;
|
||||
break;
|
||||
case EVoxelStrateParamBlock::VerticalShaft:
|
||||
LandingBaseDensity = CP_CustomParams.VerticalShaftParams.BaseDensity;
|
||||
break;
|
||||
case EVoxelStrateParamBlock::FloatingIsland:
|
||||
LandingBaseDensity = CP_CustomParams.FloatingIslandParams.BaseDensity;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (CP_GenType)
|
||||
{
|
||||
case ECaveGeneratorType::FlatPlain:
|
||||
case ECaveGeneratorType::CrystalChamber:
|
||||
LandingBaseDensity = CP_Slab.BaseDensity;
|
||||
break;
|
||||
case ECaveGeneratorType::Maze:
|
||||
LandingBaseDensity = CP_Maze.BaseDensity;
|
||||
break;
|
||||
case ECaveGeneratorType::SurfaceWorld:
|
||||
LandingBaseDensity = CP_Surface.BaseDensity;
|
||||
break;
|
||||
case ECaveGeneratorType::VerticalShafts:
|
||||
LandingBaseDensity = CP_Vert.BaseDensity;
|
||||
break;
|
||||
case ECaveGeneratorType::FloatingIslands:
|
||||
LandingBaseDensity = CP_Float.BaseDensity;
|
||||
break;
|
||||
case ECaveGeneratorType::TunnelNetwork:
|
||||
case ECaveGeneratorType::Underwater:
|
||||
default:
|
||||
LandingBaseDensity = CP_Tunnel.BaseDensity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const float LandingBaseDensity = BrickLandingBaseDensity;
|
||||
auto ApplyTunnelCoreShape = [&](const FTunnelCoreWorldEvaluation& Core)
|
||||
{
|
||||
if (!VoxelMath::IsFinite(Core.SDF))
|
||||
@@ -3538,6 +3720,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
|
||||
CP_Dist.StrateTopWorldZ, CP_Dist.StrateBottomWorldZ,
|
||||
CP_Dist.BoundarySealThickness, LandingBaseDensity, OriginSpineRadius);
|
||||
}
|
||||
if (bApplyBrickPassagePosts)
|
||||
{
|
||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||
VoxelDensityProfile::EBucket::PassageStructuralPosts);
|
||||
@@ -3567,12 +3750,14 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
|
||||
CP_Dist.StrateTopWorldZ, CP_Dist.StrateBottomWorldZ,
|
||||
CP_Dist.BoundarySealThickness, LandingBaseDensity, OriginSpineRadius);
|
||||
}
|
||||
if (bApplyBrickPassagePosts)
|
||||
{
|
||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||
VoxelDensityProfile::EBucket::PassageLandingFloor);
|
||||
StrateManager->ApplyPassageLandingFloorMC(
|
||||
Result, WorldX, WorldY, WorldZ, LandingBaseDensity);
|
||||
}
|
||||
if (bApplyBrickPassagePosts)
|
||||
{
|
||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||
VoxelDensityProfile::EBucket::PassageLandingRoomFloor);
|
||||
@@ -3647,6 +3832,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
|
||||
// composed from the immutable construction-time profile after generic writers and the
|
||||
// graph overlap, so the legacy passage support slab can be disabled without losing the
|
||||
// floor to a disturbance refill.
|
||||
if (bApplyBrickPassagePosts)
|
||||
{
|
||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||
VoxelDensityProfile::EBucket::PassageLandingFloor);
|
||||
@@ -3655,6 +3841,13 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
|
||||
}
|
||||
StructuralTailTimer.End();
|
||||
DensityStructuralPostsTimer.End();
|
||||
#if WITH_EDITOR
|
||||
if (bEvaluateDensityBatch)
|
||||
{
|
||||
GVoxelDensityBlockSession.BatchResults[DensityBatchIndex] = Result;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3673,6 +3866,33 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
|
||||
DensityCoreTimer.End();
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
const int32 FinalDensityBatchCount = bEvaluateDensityBatch ? BatchSampleCount : 1;
|
||||
#else
|
||||
const int32 FinalDensityBatchCount = 1;
|
||||
#endif
|
||||
for (int32 FinalDensityBatchIndex = 0;
|
||||
FinalDensityBatchIndex < FinalDensityBatchCount;
|
||||
++FinalDensityBatchIndex)
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
if (bEvaluateDensityBatch)
|
||||
{
|
||||
const int32 BatchX = FinalDensityBatchIndex % GVoxelDensityBlockSession.SizeX;
|
||||
const int32 BatchY = (FinalDensityBatchIndex / GVoxelDensityBlockSession.SizeX)
|
||||
% GVoxelDensityBlockSession.SizeY;
|
||||
const int32 BatchZ = FinalDensityBatchIndex
|
||||
/ (GVoxelDensityBlockSession.SizeX * GVoxelDensityBlockSession.SizeY);
|
||||
WorldX = static_cast<float>(GVoxelDensityBlockSession.Origin.X
|
||||
+ BatchX * GVoxelDensityBlockSession.Step);
|
||||
WorldY = static_cast<float>(GVoxelDensityBlockSession.Origin.Y
|
||||
+ BatchY * GVoxelDensityBlockSession.Step);
|
||||
WorldZ = static_cast<float>(GVoxelDensityBlockSession.Origin.Z
|
||||
+ BatchZ * GVoxelDensityBlockSession.Step);
|
||||
Result = GVoxelDensityBlockSession.BatchResults[FinalDensityBatchIndex];
|
||||
VoxelPassageGeometry::ResetVerticalShaftConnectorAirMarker();
|
||||
}
|
||||
#endif
|
||||
// The edge is the final generated structural invariant. Passage carving is ordered before it
|
||||
// in every archetype/stack, and this MC-facing pass also covers out-of-layout air plus any
|
||||
// disturbance that might otherwise carve back into the shell. Player edits remain separate
|
||||
@@ -3704,7 +3924,13 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
|
||||
{
|
||||
*OutPreEditDensity = Result;
|
||||
}
|
||||
if (DiffLayer && DiffLayer->HasAnyMods())
|
||||
const bool bDiffLayerHasModsForSample = DiffLayer != nullptr &&
|
||||
#if WITH_EDITOR
|
||||
(bEvaluateDensityBatch ? bBrickHasDiffMods : DiffLayer->HasAnyMods());
|
||||
#else
|
||||
DiffLayer->HasAnyMods();
|
||||
#endif
|
||||
if (DiffLayer && bDiffLayerHasModsForSample)
|
||||
{
|
||||
// ── PER-CHUNK MOD SNAPSHOT ──
|
||||
// The old HasModifications + GetDensityOffset pair took the diff layer's RWLock + a TMap find
|
||||
@@ -3737,8 +3963,23 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
|
||||
{
|
||||
Result -= UVoxelDiffLayer::EvaluateMods(Slot.Mods, WorldX, WorldY, WorldZ);
|
||||
}
|
||||
}
|
||||
DensityDiffLayerTimer.End();
|
||||
|
||||
#if WITH_EDITOR
|
||||
if (bEvaluateDensityBatch)
|
||||
{
|
||||
GVoxelDensityBlockSession.BatchResults[FinalDensityBatchIndex] = Result;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
DensityDiffLayerTimer.End();
|
||||
|
||||
#if WITH_EDITOR
|
||||
if (bEvaluateDensityBatch)
|
||||
{
|
||||
GVoxelDensityBlockSession.bBatchSucceeded = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
VoxelDensityProfile::FScopedTimer DensityTailTimer(
|
||||
VoxelDensityProfile::EBucket::DensityTail);
|
||||
|
||||
@@ -180,6 +180,13 @@ public:
|
||||
int32 SizeX, int32 SizeY, int32 SizeZ) const;
|
||||
void EndDensityBlock() const;
|
||||
|
||||
#if WITH_EDITOR
|
||||
/** Editor-only region-program entry point; unsupported routes report live point fallbacks. */
|
||||
bool EvaluateDensityBrick(FIntVector OriginVoxels, int32 SizeX, int32 SizeY, int32 SizeZ,
|
||||
TArray<float>& OutDensities,
|
||||
int32& OutFallbackSamples) const;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Prove that the mesher's core lattice is entirely solid from the cheap, structural rules
|
||||
* that own inter-strate gaps and boundary seals. This never samples density. It is intended
|
||||
|
||||
Reference in new issue
Block a user