wip(assembler): round S2 (Sol) -- shared per-tile feature queries for every Core family; island placement 30x faster
Island-only worker context replaced by a shared worker-local candidate domain (WorldAssemblyFeatureTileListV1 / ScopedWorldAssemblyFeatureQueriesV1) serving SurfaceWorld, FloatingIslands and far-shell queries; a per-domain cost hint keeps the full scan on small plans. Content placement prepares the same queries and skips proven air: owner island cell p95 7.07 -> 0.24 ms; populated fixture 572 exact placements, 3.27 s -> 29 ms. Full/candidate hashes equal with dropped-feature controls on both families; S1 default W1/W8/W16 hashes reproduced; Core-off and M2 fixtures pass. Default contract for future families in ARCHITECTURE 8.23. Run on gpt-6.1-sol medium. 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
d6e69420e8
commit
6ddb834b1c
19 files changed
+1849
-132
No files matched your search
@@ -133,6 +133,7 @@ struct FCoreFloatingIslandFieldSnapshot
|
||||
VoxelForgeCore::FloatingIslandRecipeV1 Recipe;
|
||||
VoxelForgeCore::FloatingIslandPlanV1 Plan;
|
||||
TUniquePtr<VoxelForgeCore::FloatingIslandFieldV1> Field;
|
||||
std::vector<VoxelForgeCore::WorldAssemblyFeatureBoundsV1> SolidSupportBounds;
|
||||
};
|
||||
|
||||
struct FCoreFloatingIslandSnapshot
|
||||
@@ -231,6 +232,49 @@ struct FCoreFloatingIslandSnapshot
|
||||
}
|
||||
};
|
||||
|
||||
// Shared consumer preparation; snapshots own all pointers for the entire worker lifetime.
|
||||
// Preparation partagee : les snapshots gardent les pointeurs vivants durant toute la tache.
|
||||
static std::vector<VoxelForgeCore::WorldAssemblyFeatureQueryV1> VF_ContentFeatureQueries(
|
||||
const FCoreSurfaceSnapshot* Surface, const FCoreFloatingIslandSnapshot* Islands,
|
||||
int32 StrateIndex, float MinX, float MinY, float MinZ, float MaxX, float MaxY, float MaxZ)
|
||||
{
|
||||
using namespace VoxelForgeCore;
|
||||
constexpr double Q = WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::array<int64,3> Min{static_cast<int64>(std::floor(MinX*Q)),
|
||||
static_cast<int64>(std::floor(MinY*Q)),static_cast<int64>(std::floor(MinZ*Q))};
|
||||
const std::array<int64,3> Max{static_cast<int64>(std::ceil(MaxX*Q)),
|
||||
static_cast<int64>(std::ceil(MaxY*Q)),static_cast<int64>(std::ceil(MaxZ*Q))};
|
||||
std::vector<WorldAssemblyFeatureQueryV1> Queries;
|
||||
if (Surface)
|
||||
if (const auto* Plan = Surface->FindPlan(StrateIndex))
|
||||
{
|
||||
const Heightfield2D Field(Surface->Recipe,*Plan);
|
||||
Queries.push_back({Field.recipe(),Field.plan(),Field.buildFeatureTileList(Min,Max)});
|
||||
}
|
||||
if (Islands)
|
||||
if (const auto* Entry = Islands->FindField(StrateIndex))
|
||||
Queries.push_back({&Entry->Recipe,&Entry->Plan,Entry->Field->buildFeatureTileList(Min,Max)});
|
||||
return Queries;
|
||||
}
|
||||
|
||||
// Preserve the original march lattice and crossing refinement, skipping only certified air.
|
||||
// Garder la phase du pas et le raffinement d'origine; sauter uniquement l'air prouve.
|
||||
static bool VF_ConstrainContentMarch(std::span<const VoxelForgeCore::WorldAssemblyFeatureBoundsV1> Bounds,
|
||||
float X, float Y, float Step, float& Top, float& Bottom)
|
||||
{
|
||||
constexpr double Q = VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const auto Range = VoxelForgeCore::BuildWorldAssemblyFeatureColumnRangeV1(Bounds,
|
||||
static_cast<int64>(std::llround(X*Q)),static_cast<int64>(std::llround(Y*Q)));
|
||||
if (!Range.known) return true;
|
||||
if (Range.empty) return false;
|
||||
const float SupportTop = static_cast<float>(Range.maximumZSubvoxels/Q);
|
||||
const float SupportBottom = static_cast<float>(Range.minimumZSubvoxels/Q);
|
||||
const float SkipSteps = FMath::Max(0.0f,FMath::FloorToFloat((Top-SupportTop)/Step)-1.0f);
|
||||
Top -= SkipSteps*Step;
|
||||
Bottom = FMath::Max(Bottom,SupportBottom-Step);
|
||||
return Top >= Bottom;
|
||||
}
|
||||
|
||||
// Global safety cap on spawned decoration ACTORS per cell (across all entries).
|
||||
// HISM instances are exempt — they're batched render data, capped per entry by MaxPerChunk.
|
||||
static constexpr int32 GMaxDecorationActorsPerCell = 400;
|
||||
@@ -330,6 +374,7 @@ void UVoxelContentManager::Initialize(AActor* InOwner, UVoxelStrateManager* InSt
|
||||
IslandPlan.strateIndex);
|
||||
continue;
|
||||
}
|
||||
Entry->SolidSupportBounds = Entry->Field->solidSupportBounds();
|
||||
IslandSnapshot->Fields.Add(IslandPlan.strateIndex, Entry);
|
||||
}
|
||||
if (!IslandSnapshot->Fields.IsEmpty())
|
||||
@@ -799,7 +844,7 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
|
||||
const int32 Step = FMath::Max(1, Settings->DecorationMarchStepVoxels);
|
||||
const int32 MaxCrossings = FMath::Max(1, Settings->DecorationMaxCrossingsPerColumn);
|
||||
const float ColumnDepth = static_cast<float>(FMath::Max(8, Settings->DecorationColumnDepthVoxels));
|
||||
TArray<double> CellMilliseconds;
|
||||
TArray<double> CellMilliseconds, FullScanCellMilliseconds;
|
||||
CellMilliseconds.Reserve(SampleCellCount);
|
||||
int64 TotalColumns = 0;
|
||||
int64 TotalDensityQueries = 0;
|
||||
@@ -810,7 +855,7 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
|
||||
const int32 X = Sample % GridSide - GridSide / 2;
|
||||
const int32 Y = Sample / GridSide - GridSide / 2;
|
||||
const FIntPoint Cell(PlayerCell.X + X, PlayerCell.Y + Y);
|
||||
const double CellStart = FPlatformTime::Seconds();
|
||||
double CellElapsed = 0.0, FullScanCellElapsed = 0.0;
|
||||
for (const auto& GridAudit : {
|
||||
std::make_pair(&NearGrid, &NearEntriesAudit),
|
||||
std::make_pair(&FarGrid, &FarEntriesAudit)})
|
||||
@@ -822,9 +867,26 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
|
||||
if (Entries.IsEmpty()) continue;
|
||||
TArray<FDecoSpawn> Spawns;
|
||||
FPlacementBuildCounters Counters;
|
||||
TArray<FDecoSpawn> FullScanSpawns;
|
||||
const double FullStart = FPlatformTime::Seconds();
|
||||
BuildCellSpawns(Generator, OwnerXf, Cell, CurrentCtx, Entries, EntryBiome,
|
||||
static_cast<uint32>(Seed), Grid.Spacing, static_cast<float>(Step),
|
||||
MaxCrossings, ColumnDepth, FullScanSpawns, nullptr, false);
|
||||
FullScanCellElapsed += (FPlatformTime::Seconds()-FullStart)*1000.0;
|
||||
const double CandidateStart = FPlatformTime::Seconds();
|
||||
BuildCellSpawns(Generator, OwnerXf, Cell, CurrentCtx, Entries, EntryBiome,
|
||||
static_cast<uint32>(Seed), Grid.Spacing, static_cast<float>(Step),
|
||||
MaxCrossings, ColumnDepth, Spawns, &Counters);
|
||||
CellElapsed += (FPlatformTime::Seconds()-CandidateStart)*1000.0;
|
||||
if (Spawns.Num()!=FullScanSpawns.Num())
|
||||
{ OutError=TEXT("Feature-query placement count changed from full scan."); return false; }
|
||||
for (int32 Index=0;Index<Spawns.Num();++Index)
|
||||
{
|
||||
if (Spawns[Index].EntryIdx!=FullScanSpawns[Index].EntryIdx
|
||||
|| !Spawns[Index].Xf.Equals(FullScanSpawns[Index].Xf,0.0f))
|
||||
{ OutError=TEXT("Feature-query placement transform changed from full scan."); return false; }
|
||||
++OutAudit.ExactPlacementComparisons;
|
||||
}
|
||||
TotalColumns += Counters.Columns;
|
||||
TotalDensityQueries += Counters.DensityQueries;
|
||||
TotalSurfaceQueries += Counters.SurfaceQueries;
|
||||
@@ -875,7 +937,10 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
|
||||
}
|
||||
}
|
||||
}
|
||||
CellMilliseconds.Add((FPlatformTime::Seconds() - CellStart) * 1000.0);
|
||||
CellMilliseconds.Add(CellElapsed);
|
||||
FullScanCellMilliseconds.Add(FullScanCellElapsed);
|
||||
OutAudit.ActiveBatchMilliseconds += CellElapsed;
|
||||
OutAudit.FullScanActiveBatchMilliseconds += FullScanCellElapsed;
|
||||
}
|
||||
|
||||
auto MedianAndP95 = [](TArray<double> Samples, double& OutMedian, double& OutP95)
|
||||
@@ -899,6 +964,117 @@ bool UVoxelContentManager::MeasurePlacementCostForValidation(
|
||||
MedianAndP95(MoveTemp(CellMilliseconds), OutAudit.CellMedianMilliseconds,
|
||||
OutAudit.CellP95Milliseconds);
|
||||
|
||||
double UnusedMedian = 0.0;
|
||||
MedianAndP95(MoveTemp(FullScanCellMilliseconds),UnusedMedian,OutAudit.FullScanCellP95Milliseconds);
|
||||
|
||||
// Deliberately populated fixture alongside the unmodified authored palette. No actors are spawned.
|
||||
// Fixture volontairement peuplee, a cote de la palette auteur intacte. Aucun acteur cree.
|
||||
UStaticMesh* FixtureMesh=nullptr;
|
||||
for (const auto* Entries : {&NearEntriesAudit,&FarEntriesAudit})
|
||||
for (const auto& Entry : *Entries)
|
||||
if (!FixtureMesh && Entry.Profile.InstancedMesh) FixtureMesh=Entry.Profile.InstancedMesh;
|
||||
if (!FixtureMesh)
|
||||
FixtureMesh=LoadObject<UStaticMesh>(nullptr,TEXT("/Engine/BasicShapes/Cube.Cube"));
|
||||
TMap<FIntPoint,FDecoRegionBuild> FixtureBuilds;
|
||||
TArray<FStrateDecoration> FixtureEntries;
|
||||
TArray<int32> FixtureBiomes;
|
||||
const int32 BiomeCount = CurrentCtx.BiomeCtx.IsValid() ? CurrentCtx.BiomeCtx.Biomes.Num() : 0;
|
||||
for (int32 Biome=0;Biome<FMath::Max(1,BiomeCount);++Biome)
|
||||
for (int32 Wet=0;Wet<2;++Wet)
|
||||
{
|
||||
FStrateDecoration Entry;
|
||||
Entry.Profile.ActorClass=AActor::StaticClass();
|
||||
Entry.Profile.InstancedMesh=FixtureMesh;
|
||||
Entry.Profile.SurfacePlacement=ESurfaceType::Floor;
|
||||
Entry.Profile.bPlaceBelowWater=Wet!=0;
|
||||
Entry.SpawnDensity=1.0f;
|
||||
Entry.MaxPerChunk=64;
|
||||
FixtureEntries.Add(Entry);
|
||||
FixtureBiomes.Add(BiomeCount ? Biome : -1);
|
||||
}
|
||||
TArray<double> PopulatedTimes, FullPopulatedTimes;
|
||||
for (int32 Sample=0;Sample<SampleCellCount;++Sample)
|
||||
{
|
||||
const FIntPoint Cell(PlayerCell.X+Sample%GridSide-GridSide/2,
|
||||
PlayerCell.Y+Sample/GridSide-GridSide/2);
|
||||
TArray<FDecoSpawn> FullSpawns, Spawns;
|
||||
const double FullStart=FPlatformTime::Seconds();
|
||||
BuildCellSpawns(Generator,OwnerXf,Cell,CurrentCtx,FixtureEntries,FixtureBiomes,
|
||||
static_cast<uint32>(Seed),4,static_cast<float>(Step),MaxCrossings,ColumnDepth,
|
||||
FullSpawns,nullptr,false);
|
||||
const double FullElapsed=(FPlatformTime::Seconds()-FullStart)*1000.0;
|
||||
const double CandidateStart=FPlatformTime::Seconds();
|
||||
BuildCellSpawns(Generator,OwnerXf,Cell,CurrentCtx,FixtureEntries,FixtureBiomes,
|
||||
static_cast<uint32>(Seed),4,static_cast<float>(Step),MaxCrossings,ColumnDepth,Spawns);
|
||||
const double Elapsed=(FPlatformTime::Seconds()-CandidateStart)*1000.0;
|
||||
OutAudit.PopulatedBatchMilliseconds+=Elapsed;
|
||||
OutAudit.FullScanPopulatedBatchMilliseconds+=FullElapsed;
|
||||
if (Spawns.Num()!=FullSpawns.Num())
|
||||
{ OutError=TEXT("Populated fixture count differs from full scan."); return false; }
|
||||
const double MergeStart=FPlatformTime::Seconds();
|
||||
if (FixtureMesh && !Spawns.IsEmpty())
|
||||
{
|
||||
auto& Build=FixtureBuilds.FindOrAdd(CellToRegion(Cell,RegionSize()));
|
||||
auto& Bucket=Build.MeshBuckets.FindOrAdd(FixtureMesh);
|
||||
for (const auto& Spawn : Spawns)
|
||||
{
|
||||
Bucket.Profile=FixtureEntries[Spawn.EntryIdx].Profile;
|
||||
Bucket.Xforms.Add(Spawn.Xf);
|
||||
}
|
||||
}
|
||||
OutAudit.PopulatedMergeMilliseconds+=(FPlatformTime::Seconds()-MergeStart)*1000.0;
|
||||
for (int32 Index=0;Index<Spawns.Num();++Index)
|
||||
{
|
||||
if (!Spawns[Index].Xf.Equals(FullSpawns[Index].Xf,0.0f))
|
||||
{ OutError=TEXT("Populated fixture anchor differs from full scan."); return false; }
|
||||
++OutAudit.ExactPlacementComparisons;
|
||||
const FVector Local=OwnerXf.InverseTransformPosition(Spawns[Index].Xf.GetLocation());
|
||||
float Density=0.0f;
|
||||
if (CurrentCtx.bNativeFloatingIslands && CurrentCtx.NativeIslands.IsValid()
|
||||
&& CurrentCtx.NativeIslands->GetDensity(CurrentCtx.StrateIndex,
|
||||
Local.X/CurrentCtx.VoxelSizeCm,Local.Y/CurrentCtx.VoxelSizeCm,
|
||||
Local.Z/CurrentCtx.VoxelSizeCm,Density))
|
||||
{
|
||||
OutAudit.MaximumDensityResidual=FMath::Max(OutAudit.MaximumDensityResidual,
|
||||
FMath::Abs(static_cast<double>(Density)));
|
||||
++OutAudit.AnchorChecks;
|
||||
if (FMath::Abs(Density)>0.25f)
|
||||
{ OutError=TEXT("Populated fixture is not anchored on the canonical field."); return false; }
|
||||
}
|
||||
}
|
||||
if (!Spawns.IsEmpty())
|
||||
{
|
||||
++OutAudit.PopulatedCells;
|
||||
OutAudit.PopulatedPlacements+=Spawns.Num();
|
||||
PopulatedTimes.Add(Elapsed);
|
||||
FullPopulatedTimes.Add(FullElapsed);
|
||||
}
|
||||
}
|
||||
if ((CurrentCtx.bNativeSurface||CurrentCtx.bNativeFloatingIslands) && OutAudit.PopulatedPlacements==0)
|
||||
{ OutError=TEXT("Populated content fixture produced no placements."); return false; }
|
||||
if (FixtureMesh && OutAudit.PopulatedPlacements>0)
|
||||
{
|
||||
// Exercise actual region application, then close this isolated fixture immediately.
|
||||
// Appliquer une vraie region puis detruire immediatement cette fixture isolee.
|
||||
FDecoGrid FixtureGrid;
|
||||
const double ApplyStart=FPlatformTime::Seconds();
|
||||
// Fixture footprints are isolated from live landmark suppression. / Isoler les empreintes de fixture.
|
||||
for (auto& Pair : FixtureBuilds) ApplyRegion(FixtureGrid,Pair.Key,Pair.Value,false);
|
||||
OutAudit.PopulatedApplyMilliseconds=(FPlatformTime::Seconds()-ApplyStart)*1000.0;
|
||||
for (const auto& Pair : FixtureGrid.Regions)
|
||||
for (const auto& WeakHISM : Pair.Value.Instances)
|
||||
if (const auto* HISM=WeakHISM.Get()) OutAudit.AppliedInstances+=HISM->GetInstanceCount();
|
||||
for (const auto& Pair : FixtureBuilds) ClearDecorationRegion(FixtureGrid,Pair.Key);
|
||||
if (OutAudit.AppliedInstances!=OutAudit.PopulatedPlacements)
|
||||
{
|
||||
OutError=FString::Printf(TEXT("Populated HISM fixture applied %d of %d placements (%d regions, mesh %s)."),
|
||||
OutAudit.AppliedInstances,OutAudit.PopulatedPlacements,FixtureBuilds.Num(),*FixtureMesh->GetPathName());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
MedianAndP95(MoveTemp(PopulatedTimes),UnusedMedian,OutAudit.PopulatedCellP95Milliseconds);
|
||||
MedianAndP95(MoveTemp(FullPopulatedTimes),UnusedMedian,OutAudit.FullScanPopulatedCellP95Milliseconds);
|
||||
|
||||
// The game-thread update cost is sampled with task dispatch disabled; worker-cell cost is reported
|
||||
// separately above, so the frame figure is not polluted by waiting for asynchronous work.
|
||||
UpdateDecorations(PlayerWorldPos);
|
||||
@@ -1092,7 +1268,7 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
|
||||
const TArray<int32>& EntryBiome, uint32 InSeed,
|
||||
int32 Spacing, float Step, int32 MaxCrossings, float ColumnDepth,
|
||||
TArray<FDecoSpawn>& OutSpawns,
|
||||
FPlacementBuildCounters* OutCounters)
|
||||
FPlacementBuildCounters* OutCounters, bool bUseFeatureQueries)
|
||||
{
|
||||
if (OutCounters != nullptr) *OutCounters = FPlacementBuildCounters();
|
||||
if (!Gen || Entries.Num() == 0) return;
|
||||
@@ -1101,6 +1277,14 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
|
||||
const int32 CellOriginVX = Cell.X * CHUNK_SIZE;
|
||||
const int32 CellOriginVY = Cell.Y * CHUNK_SIZE;
|
||||
|
||||
const auto FeatureQueries = bUseFeatureQueries ? VF_ContentFeatureQueries(
|
||||
Ctx.bNativeSurface ? Ctx.NativeSurface.Get() : nullptr,
|
||||
Ctx.bNativeFloatingIslands ? Ctx.NativeIslands.Get() : nullptr,Ctx.StrateIndex,
|
||||
CellOriginVX-1.0f,CellOriginVY-1.0f,Ctx.BottomVoxelZ-1.0f,
|
||||
CellOriginVX+CHUNK_SIZE+1.0f,CellOriginVY+CHUNK_SIZE+1.0f,Ctx.TopVoxelZ+1.0f)
|
||||
: std::vector<VoxelForgeCore::WorldAssemblyFeatureQueryV1>{};
|
||||
const VoxelForgeCore::ScopedWorldAssemblyFeatureQueriesV1 FeatureScope(FeatureQueries);
|
||||
|
||||
TArray<int32> EntryCount; EntryCount.Init(0, Entries.Num());
|
||||
int32 TotalActors = 0;
|
||||
|
||||
@@ -1437,12 +1621,17 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
|
||||
}
|
||||
|
||||
// NON-SURFACE archetypes (caves/shafts/islands): ray-march the density column for crossings.
|
||||
float PrevD = D(VX, VY, Ctx.TopVoxelZ);
|
||||
float MarchTop = Ctx.TopVoxelZ, MarchBottom = Ctx.BottomVoxelZ;
|
||||
if (bUseFeatureQueries && Ctx.bNativeFloatingIslands && Ctx.NativeIslands.IsValid())
|
||||
if (const auto* Entry = Ctx.NativeIslands->FindField(Ctx.StrateIndex))
|
||||
if (!VF_ConstrainContentMarch(Entry->SolidSupportBounds,VX,VY,Step,MarchTop,MarchBottom))
|
||||
continue;
|
||||
float PrevD = D(VX, VY, MarchTop);
|
||||
int32 Crossings = 0;
|
||||
bool bSeenAir = (PrevD >= 0.0f); // GetDensityAt >= 0 == air
|
||||
float SolidRun = 0.0f; // contiguous solid voxels since the last open air
|
||||
|
||||
for (float Z = Ctx.TopVoxelZ - Step; Z >= Ctx.BottomVoxelZ && Crossings < MaxCrossings; Z -= Step)
|
||||
for (float Z = MarchTop - Step; Z >= MarchBottom && Crossings < MaxCrossings; Z -= Step)
|
||||
{
|
||||
const float Dz = D(VX, VY, Z);
|
||||
if ((PrevD >= 0.0f) != (Dz >= 0.0f)) // straddles IsoLevel 0 (air ↔ solid)
|
||||
@@ -1615,7 +1804,8 @@ void UVoxelContentManager::MarkCellDone(FDecoGrid& G, const FIntPoint& Region, c
|
||||
|
||||
// Build the region's components: one HISM per mesh (all cells merged → one batched AddInstances), actors
|
||||
// spawned inline. Moves the region into G.Regions; the build is removed by the caller.
|
||||
void UVoxelContentManager::ApplyRegion(FDecoGrid& G, const FIntPoint& Region, FDecoRegionBuild& Build)
|
||||
void UVoxelContentManager::ApplyRegion(FDecoGrid& G, const FIntPoint& Region, FDecoRegionBuild& Build,
|
||||
bool bApplyLandmarkFootprints)
|
||||
{
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_DecoApply); // total game-thread cost to apply one region
|
||||
|
||||
@@ -1714,7 +1904,7 @@ void UVoxelContentManager::ApplyRegion(FDecoGrid& G, const FIntPoint& Region, FD
|
||||
// but this region just streamed in FRESH → re-clear under any loaded suppressing landmark overlapping it
|
||||
// (the per-HISM spatial query early-outs for the non-overlapping majority). Keeps temple floors clear as
|
||||
// you leave and return.
|
||||
if (Content.Instances.Num() > 0)
|
||||
if (bApplyLandmarkFootprints && Content.Instances.Num() > 0)
|
||||
{
|
||||
for (const TPair<FIntVector, FLandmarkInstance>& LP : LandmarkInstances)
|
||||
{
|
||||
@@ -1781,6 +1971,11 @@ bool UVoxelContentManager::FindLandmarkColumn(const UVoxelGenerator* Gen, const
|
||||
{
|
||||
if (!Gen) return false;
|
||||
|
||||
const auto FeatureQueries = VF_ContentFeatureQueries(
|
||||
Ctx.bNativeSurface ? Ctx.NativeSurface.Get() : nullptr,
|
||||
Ctx.bNativeFloatingIslands ? Ctx.NativeIslands.Get() : nullptr,Ctx.StrateIndex,
|
||||
VX-1.0f,VY-1.0f,Ctx.BottomVoxelZ-1.0f,VX+1.0f,VY+1.0f,Ctx.TopVoxelZ+1.0f);
|
||||
const VoxelForgeCore::ScopedWorldAssemblyFeatureQueriesV1 FeatureScope(FeatureQueries);
|
||||
const auto Density = [&](float SampleX, float SampleY, float SampleZ)
|
||||
{
|
||||
if (Ctx.bNativeFloatingIslands && Ctx.NativeIslands.IsValid())
|
||||
@@ -1852,10 +2047,15 @@ bool UVoxelContentManager::FindLandmarkColumn(const UVoxelGenerator* Gen, const
|
||||
// Bounded by ColDepth like the decoration march (this runs SYNCHRONOUSLY on the game thread):
|
||||
// once past open air, a solid run longer than ColDepth means bedrock down to the strate floor —
|
||||
// stop instead of paying GetDensityAt across the whole remaining band.
|
||||
float PrevD = Density(VX, VY, Ctx.TopVoxelZ);
|
||||
float MarchTop = Ctx.TopVoxelZ, MarchBottom = Ctx.BottomVoxelZ;
|
||||
if (Ctx.bNativeFloatingIslands && Ctx.NativeIslands.IsValid())
|
||||
if (const auto* Entry = Ctx.NativeIslands->FindField(Ctx.StrateIndex))
|
||||
if (!VF_ConstrainContentMarch(Entry->SolidSupportBounds,VX,VY,Step,MarchTop,MarchBottom))
|
||||
return false;
|
||||
float PrevD = Density(VX, VY, MarchTop);
|
||||
bool bSeenAir = (PrevD >= 0.0f);
|
||||
float SolidRun = 0.0f;
|
||||
for (float Z = Ctx.TopVoxelZ - Step; Z >= Ctx.BottomVoxelZ; Z -= Step)
|
||||
for (float Z = MarchTop - Step; Z >= MarchBottom; Z -= Step)
|
||||
{
|
||||
const float Dz = Density(VX, VY, Z);
|
||||
if ((PrevD >= 0.0f) != (Dz >= 0.0f)) // air ↔ solid crossing
|
||||
|
||||
@@ -137,6 +137,19 @@ public:
|
||||
double MaximumDensityResidual = 0.0;
|
||||
double DensityQueriesPerCell = 0.0;
|
||||
double SurfaceQueriesPerCell = 0.0;
|
||||
double FullScanCellP95Milliseconds = 0.0;
|
||||
double ActiveBatchMilliseconds = 0.0;
|
||||
double FullScanActiveBatchMilliseconds = 0.0;
|
||||
double PopulatedBatchMilliseconds = 0.0;
|
||||
double FullScanPopulatedBatchMilliseconds = 0.0;
|
||||
double PopulatedMergeMilliseconds = 0.0;
|
||||
double PopulatedApplyMilliseconds = 0.0;
|
||||
int32 AppliedInstances = 0;
|
||||
double PopulatedCellP95Milliseconds = 0.0;
|
||||
double FullScanPopulatedCellP95Milliseconds = 0.0;
|
||||
int32 PopulatedPlacements = 0;
|
||||
int32 PopulatedCells = 0;
|
||||
int32 ExactPlacementComparisons = 0;
|
||||
double CellMedianMilliseconds = 0.0;
|
||||
double CellP95Milliseconds = 0.0;
|
||||
double UpdateMedianMilliseconds = 0.0;
|
||||
@@ -338,7 +351,7 @@ private:
|
||||
const TArray<int32>& EntryBiome, uint32 InSeed,
|
||||
int32 Spacing, float Step, int32 MaxCrossings, float ColumnDepth,
|
||||
TArray<FDecoSpawn>& OutSpawns,
|
||||
FPlacementBuildCounters* OutCounters = nullptr);
|
||||
FPlacementBuildCounters* OutCounters = nullptr, bool bUseFeatureQueries = true);
|
||||
|
||||
// Each step operates on ONE grid (G = NearGrid or FarGrid). LaunchDecoTasks throttles against the
|
||||
// COMBINED in-flight count (OtherInFlight = the other grid's in-flight cells) so the two grids share
|
||||
@@ -348,7 +361,8 @@ private:
|
||||
void ProcessDecoResults(const FIntPoint& PlayerCell);
|
||||
void MergeCellResult(FDecoGrid& G, const FDecoCellResult& Result); // fold one cell's spawns into its region build
|
||||
void MarkCellDone(FDecoGrid& G, const FIntPoint& Region, const FIntPoint& Cell, uint32 BuildId); // idempotent per-cell accounting
|
||||
void ApplyRegion(FDecoGrid& G, const FIntPoint& Region, FDecoRegionBuild& Build);
|
||||
void ApplyRegion(FDecoGrid& G, const FIntPoint& Region, FDecoRegionBuild& Build,
|
||||
bool bApplyLandmarkFootprints = true);
|
||||
void RebuildDesiredCells(FDecoGrid& G, const FIntPoint& PlayerCell);
|
||||
void ClearDecorationRegion(FDecoGrid& G, const FIntPoint& Region);
|
||||
void ClearAllDecorations();
|
||||
|
||||
Reference in new issue
Block a user