fix(gen): the phantom terracing is the PASSAGE support writer -- plus a region verdict cache
The owner played the build and reported four things. Three are answered with evidence, one is disproven, and my own leading hypothesis was wrong. ⭐ 1. THE PHANTOM TERRACING IS NOT MARCHING-CUBES ALIASING He reported stairs surviving with bTunnelFloorTerracingEnabled = false. I suspected MC lattice aliasing on a near-planar floor; I was wrong. An A/B over the same mesher, counted in pixels: graph-tunnel floor removed 2,813 / 147,456 px changed 1.91% passage support removed 25,695 / 147,456 px changed 17.43% ⇒ The bands are the INTER-STRATE PASSAGE SUPPORT WRITER, which is independent of the tunnel terracing flag. That is exactly why turning terracing off did nothing: he was disabling the wrong system. FCaveTerraceMod is inactive (TerraceStepHeight=0) and was never involved. ⚠️ It cannot simply be switched off. With it disabled: fit 20,804 -> 20,511, walk-reachable 10,884 -> 7,525, components 260 -> 272. Route still connected, but a third of reachable space gone. It stays as a correctness backstop. The native fix is the same pattern we just applied to tunnels: author the inter-strate passage floor profile during passage CONSTRUCTION. Codex judged that refactor too large to risk this round and said so rather than attempting it. MC aliasing remains theoretically possible at shallow angles (Lorensen & Cline 1987), but the same-mesher A/B proves our writer dominates. Dual Contouring (Ju et al.) was considered and rejected as a disproportionate mesher/topology change. 2. THE ROUGHNESS COST DEFECT DOES NOT EXIST -- my hypothesis, cleanly disproven FBM 2.91s / 66.92% skips · Ridged 2.85s / 66.53% · Mixed 2.92s / 66.82% Cellular 2.99s / 66.43% · FBM frequency 0.025-0.5: 2.88-2.99s, 66.80-67.04% I predicted a shared bound across noise types collapsing the classifier. The code already carries separate conservative bounds per type. No roughness code was changed. ⇒ Whatever the owner saw is not steady-state export cost. Most likely the in-editor regeneration a parameter change triggers, which is the full-rebuild cost, not the per-sample cost. Worth confirming with him. 3. ROOM MOUTH RISE -- his idea, implemented, off by default RoomMouthRiseBlendVoxels, default 8.0 blend 8 fit 20,804 reach 10,884 40,429 v / 75,416 t capability-NEUTRAL blend 24 fit 20,792 reach 10,874 40,337 v / 75,232 t REJECTED, loses capability Relief preserved at blend 8 (dihedral p50 2.5584, p90 33.7191). The chosen camera did not see a mouth bench, so no visual claim is made. 4. ⭐ VERDICT CACHE WITH REGION REUSE -- answers "why re-prove what we proved?" 130 classification calls -> 1 store, 129 hits, of which 128 ENCLOSING-REGION hits. The region level is the one that matters: deep rock is empty thousands of chunks at a time, and one proof over a large box now serves all of them. Soundness, which is the whole risk (a stale "empty" silently deletes geometry): 512-entry bounded LRU; stores ONLY AllAir/AllSolid, never Mixed; key includes seed, layout/lifetime id, params fingerprint, diff version, density/seal settings, support mode, lattice step, origin and extent; dynamic season/composer/terrain-op/biome configs FAIL CLOSED and bypass reuse. Deliberately session-resident, not on disk -- cross-process persistence cannot serialise all asset/layout/edit state safely yet. VERIFIED BY ME: build 0 errors; export 1.353 / 1.430 / 1.432 s (2260c4dwas 1.320 / 1.424 / 1.439); geometry SHA-256 b3e5f4c3..9b377 IDENTICAL to2260c4d. Peak RSS ~1.75 GB. Determinism repeat equal. ⚠️ My composer hypothesis was also wrong: I suspected `bTunnelFloorTerracingEnabled = CategoryRng.FRand() < 0.75f` in the composer was overriding his setting. It is not the cause here. The design question it raises is still open and worth the owner's view: should a composer roll be allowed to overwrite an explicitly authored value? Otherwise "I turned it off" never reliably means anything. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
This commit is contained in:
1 parent
2260c4d47a
commit
4f8b4a9a27
10 files changed
+952
-10
No files matched your search
@@ -1333,6 +1333,11 @@ VoxelDensityProfile::FCacheMemoryBreakdown FChunkSDFCache::GetAllocatedSizeBreak
|
||||
Breakdown.TunnelsBytes += ArrayBytes(Tunnel.WorldFloorProfiles);
|
||||
}
|
||||
|
||||
for (const FCachedRoom& Room : Rooms)
|
||||
{
|
||||
Breakdown.RoomsBytes += ArrayBytes(Room.MouthRises);
|
||||
}
|
||||
|
||||
return Breakdown;
|
||||
}
|
||||
|
||||
@@ -1356,6 +1361,15 @@ struct FBuildRoom
|
||||
bool bPlayerFitAttempted = false;
|
||||
};
|
||||
|
||||
struct FBuildRoomMouthRise
|
||||
{
|
||||
int32 RoomIndex = INDEX_NONE;
|
||||
FVector Mouth = FVector::ZeroVector;
|
||||
float TargetFloorZ = 0.0f;
|
||||
float BlendRadius = 0.0f;
|
||||
float Strength = 0.0f;
|
||||
};
|
||||
|
||||
struct FVFRoomGraphReach
|
||||
{
|
||||
float DirectRoomReach = 0.0f;
|
||||
@@ -1395,6 +1409,8 @@ static bool VF_ComputeRoomGraphReach(
|
||||
Params.RoomFloorCutMax,
|
||||
Params.FloorReliefStrength,
|
||||
Params.FloorReliefFrequency,
|
||||
Params.RoomMouthRiseStrength,
|
||||
Params.RoomMouthRiseBlendVoxels,
|
||||
Params.OriginRoomRadius,
|
||||
Params.TunnelMinRadius,
|
||||
Params.TunnelMaxRadius,
|
||||
@@ -5112,6 +5128,7 @@ void VoxelCaveMorphology::BuildChunkCache(
|
||||
// set (and each candidate's own candidates) lies inside the COLLECT region.
|
||||
TArray<bool, TInlineAllocator<64>> RoomConnected;
|
||||
RoomConnected.Init(false, NumRooms);
|
||||
TArray<FBuildRoomMouthRise, TInlineAllocator<64>> RoomMouthRises;
|
||||
|
||||
const auto ResolvePlayerFitPoint = [](FBuildRoom& Room, const FStrateGenerationParams& InParams,
|
||||
uint32 InWorldSeed) -> bool
|
||||
@@ -5426,6 +5443,59 @@ void VoxelCaveMorphology::BuildChunkCache(
|
||||
- FMath::Abs(CT.WorldControlRadii.Last());
|
||||
}
|
||||
|
||||
// Author optional room-side benches now that the tunnel mouth floors are known. A
|
||||
// rise is admitted only toward a higher mouth and only when the room's own ceiling
|
||||
// still contains the player capsule. The evaluator later reads this descriptor after
|
||||
// the room's base floor is resolved; no per-sample tunnel search or correction is
|
||||
// involved. Strength zero is the exact legacy path.
|
||||
const float MouthRiseStrength = FMath::Clamp(
|
||||
FMath::IsFinite(Params.RoomMouthRiseStrength)
|
||||
? Params.RoomMouthRiseStrength : 0.0f,
|
||||
0.0f, 1.0f);
|
||||
const float MouthRiseBlend = FMath::Clamp(
|
||||
FMath::IsFinite(Params.RoomMouthRiseBlendVoxels)
|
||||
? Params.RoomMouthRiseBlendVoxels : 8.0f,
|
||||
8.0f, 128.0f);
|
||||
const float RequiredRoomClearance =
|
||||
VoxelPassageGeometry::PlayerHeightVoxels
|
||||
+ VoxelPassageGeometry::HeadroomVoxels;
|
||||
const auto AddRoomMouthRise = [
|
||||
&RoomMouthRises, &RoomFloorZFor, MouthRiseStrength,
|
||||
MouthRiseBlend, RequiredRoomClearance](
|
||||
int32 RoomIndex, const FBuildRoom& Room,
|
||||
const FVector& Mouth, float TargetFloorZ)
|
||||
{
|
||||
if (!(MouthRiseStrength > 0.0f)
|
||||
|| !FMath::IsFinite(TargetFloorZ)
|
||||
|| !FMath::IsFinite(Mouth.X)
|
||||
|| !FMath::IsFinite(Mouth.Y)
|
||||
|| !FMath::IsFinite(Mouth.Z))
|
||||
{
|
||||
return;
|
||||
}
|
||||
const float BaseFloorZ = RoomFloorZFor(Room);
|
||||
if (!FMath::IsFinite(BaseFloorZ)
|
||||
|| TargetFloorZ <= BaseFloorZ + KINDA_SMALL_NUMBER
|
||||
|| Room.Center.Z + Room.RadiusZ - TargetFloorZ
|
||||
< RequiredRoomClearance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
FBuildRoomMouthRise& Rise = RoomMouthRises.Emplace_GetRef();
|
||||
Rise.RoomIndex = RoomIndex;
|
||||
Rise.Mouth = Mouth;
|
||||
Rise.TargetFloorZ = TargetFloorZ;
|
||||
Rise.BlendRadius = MouthRiseBlend;
|
||||
Rise.Strength = MouthRiseStrength;
|
||||
};
|
||||
if (CT.ControlPoints.Num() >= 2)
|
||||
{
|
||||
AddRoomMouthRise(
|
||||
I, RoomA, CT.ControlPoints[0], CT.SDFMouthFloorZA);
|
||||
AddRoomMouthRise(
|
||||
J, RoomB, CT.ControlPoints.Last(), CT.SDFMouthFloorZB);
|
||||
}
|
||||
|
||||
CT.Midpoint = CT.ControlPoints.Num() > 2
|
||||
? CT.ControlPoints[CT.ControlPoints.Num() / 2]
|
||||
: FVector::ZeroVector;
|
||||
@@ -5838,6 +5908,28 @@ void VoxelCaveMorphology::BuildChunkCache(
|
||||
}
|
||||
}
|
||||
|
||||
// Attach the complete room-side mouth descriptors after the room shape/floor fields are
|
||||
// baked. Expand the conservative room cull to cover the local blend even when the mouth
|
||||
// is offset from the room centre.
|
||||
for (const FBuildRoomMouthRise& AuthoredRise : RoomMouthRises)
|
||||
{
|
||||
if (AuthoredRise.RoomIndex != RoomIdx)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
FCachedRoomMouthRise& Rise = CR.MouthRises.Emplace_GetRef();
|
||||
Rise.Mouth = AuthoredRise.Mouth;
|
||||
Rise.TargetFloorZ = AuthoredRise.TargetFloorZ;
|
||||
Rise.BlendRadius = AuthoredRise.BlendRadius;
|
||||
Rise.Strength = AuthoredRise.Strength;
|
||||
const float Reach = static_cast<float>(FVector::Dist(
|
||||
CR.Center, Rise.Mouth)) + FMath::Max(Rise.BlendRadius, 0.0f);
|
||||
if (FMath::IsFinite(Reach))
|
||||
{
|
||||
CR.CullRadiusSq = FMath::Max(CR.CullRadiusSq, Reach * Reach);
|
||||
}
|
||||
}
|
||||
|
||||
OutCache.Rooms.Add(CR);
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
@@ -5982,6 +6074,27 @@ float VoxelCaveMorphology::EvaluateSDFCached(
|
||||
if (Room.FloorCutZ > -FLT_MAX)
|
||||
{
|
||||
float FloorZ = Room.FloorCutZ;
|
||||
for (const FCachedRoomMouthRise& Rise : Room.MouthRises)
|
||||
{
|
||||
const float DX = static_cast<float>(Pos.X - Rise.Mouth.X);
|
||||
const float DY = static_cast<float>(Pos.Y - Rise.Mouth.Y);
|
||||
const float Radius = FMath::Max(Rise.BlendRadius, 1.0f);
|
||||
const float Distance = FMath::Sqrt(DX * DX + DY * DY);
|
||||
if (Distance >= Radius
|
||||
|| !(Rise.Strength > 0.0f)
|
||||
|| !FMath::IsFinite(Rise.TargetFloorZ))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float Weight = 1.0f - SmoothStep01(
|
||||
FMath::Clamp(Distance / Radius, 0.0f, 1.0f));
|
||||
const float DesiredFloorZ = FMath::Lerp(
|
||||
FloorZ, Rise.TargetFloorZ,
|
||||
Weight * FMath::Clamp(Rise.Strength, 0.0f, 1.0f));
|
||||
// A lower tunnel never cuts down into the room. The room remains the owner of
|
||||
// its floor, while a higher mouth receives a deliberate, smooth bench.
|
||||
FloorZ = FMath::Max(FloorZ, DesiredFloorZ);
|
||||
}
|
||||
const float FloorBlend = BlendK * 0.35f;
|
||||
bool bFloorCutIsIdentity = false;
|
||||
if (Room.FloorReliefStrength > 0.0f)
|
||||
|
||||
@@ -432,6 +432,13 @@ namespace VoxelDensityProfile
|
||||
case ECounter::MesherBlockTests: return TEXT("MesherBlockTests");
|
||||
case ECounter::MesherBlockAllSolid:return TEXT("MesherBlockAllSolid");
|
||||
case ECounter::MesherBlockAllAir: return TEXT("MesherBlockAllAir");
|
||||
case ECounter::TileClassifyCalls: return TEXT("TileClassifyCalls");
|
||||
case ECounter::TileClassifyAllSolid: return TEXT("TileClassifyAllSolid");
|
||||
case ECounter::TileClassifyAllAir: return TEXT("TileClassifyAllAir");
|
||||
case ECounter::TileClassifyMixed: return TEXT("TileClassifyMixed");
|
||||
case ECounter::TileVerdictCacheHits: return TEXT("TileVerdictCacheHits");
|
||||
case ECounter::TileVerdictRegionHits: return TEXT("TileVerdictRegionHits");
|
||||
case ECounter::TileVerdictCacheStores: return TEXT("TileVerdictCacheStores");
|
||||
case ECounter::Count: break;
|
||||
}
|
||||
return TEXT("Unknown");
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "VoxelDensityProfile.h"
|
||||
#include "ProfilingDebugging/CpuProfilerTrace.h"
|
||||
#include "HAL/PlatformTime.h"
|
||||
#include "Misc/ScopeLock.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "VoxelStrateComposer.h"
|
||||
@@ -1314,6 +1315,169 @@ UVoxelGenerator::UVoxelGenerator()
|
||||
{
|
||||
}
|
||||
|
||||
void UVoxelGenerator::InvalidateTileVerdictCache() const
|
||||
{
|
||||
FScopeLock Lock(&TileVerdictCacheLock);
|
||||
TileVerdictCache.Reset();
|
||||
TileVerdictCacheClock = 0;
|
||||
}
|
||||
|
||||
bool UVoxelGenerator::FindTileVerdict(
|
||||
const FIntVector& OriginVoxels, int32 Step, int32 CellsPerAxis,
|
||||
int32 ManagerWorldSeed, uint32 LayoutVersion, uint32 DiffVersion,
|
||||
uint64 ParamsFingerprint, uint8 PassageSupportFloorWrites,
|
||||
EVoxelTileClass& OutVerdict, bool& bOutRegionHit) const
|
||||
{
|
||||
bOutRegionHit = false;
|
||||
FScopeLock Lock(&TileVerdictCacheLock);
|
||||
++TileVerdictCacheClock;
|
||||
|
||||
const int64 RequestMaxX = static_cast<int64>(OriginVoxels.X)
|
||||
+ static_cast<int64>(CellsPerAxis) * Step;
|
||||
const int64 RequestMaxY = static_cast<int64>(OriginVoxels.Y)
|
||||
+ static_cast<int64>(CellsPerAxis) * Step;
|
||||
const int64 RequestMaxZ = static_cast<int64>(OriginVoxels.Z)
|
||||
+ static_cast<int64>(CellsPerAxis) * Step;
|
||||
|
||||
int32 BestIndex = INDEX_NONE;
|
||||
bool bBestExact = false;
|
||||
for (int32 Index = 0; Index < TileVerdictCache.Num(); ++Index)
|
||||
{
|
||||
FTileVerdictCacheEntry& Entry = TileVerdictCache[Index];
|
||||
if (Entry.GeneratorSeed != Seed
|
||||
|| Entry.ManagerWorldSeed != ManagerWorldSeed
|
||||
|| Entry.OriginSpineRadius != OriginSpineRadius
|
||||
|| Entry.WorldRadiusVoxels != WorldRadiusVoxels
|
||||
|| Entry.EdgeSealThickness != EdgeSealThickness
|
||||
|| Entry.ManagerLifetimeId != (StrateManager ? StrateManager->GetCacheLifetimeId() : 0)
|
||||
|| Entry.LayoutVersion != LayoutVersion
|
||||
|| Entry.DiffVersion != DiffVersion
|
||||
|| Entry.ParamsFingerprint != ParamsFingerprint
|
||||
|| Entry.PassageSupportFloorWrites != PassageSupportFloorWrites
|
||||
|| Entry.Step != Step
|
||||
|| Entry.CellsPerAxis < CellsPerAxis)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const int64 EntryMaxX = static_cast<int64>(Entry.Origin.X)
|
||||
+ static_cast<int64>(Entry.CellsPerAxis) * Step;
|
||||
const int64 EntryMaxY = static_cast<int64>(Entry.Origin.Y)
|
||||
+ static_cast<int64>(Entry.CellsPerAxis) * Step;
|
||||
const int64 EntryMaxZ = static_cast<int64>(Entry.Origin.Z)
|
||||
+ static_cast<int64>(Entry.CellsPerAxis) * Step;
|
||||
const int64 DeltaX = static_cast<int64>(OriginVoxels.X) - Entry.Origin.X;
|
||||
const int64 DeltaY = static_cast<int64>(OriginVoxels.Y) - Entry.Origin.Y;
|
||||
const int64 DeltaZ = static_cast<int64>(OriginVoxels.Z) - Entry.Origin.Z;
|
||||
if (DeltaX < 0 || DeltaY < 0 || DeltaZ < 0
|
||||
|| (DeltaX % Step) != 0 || (DeltaY % Step) != 0 || (DeltaZ % Step) != 0
|
||||
|| RequestMaxX > EntryMaxX || RequestMaxY > EntryMaxY || RequestMaxZ > EntryMaxZ)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool bExact = Entry.Origin == OriginVoxels
|
||||
&& Entry.CellsPerAxis == CellsPerAxis;
|
||||
if (BestIndex == INDEX_NONE || (bExact && !bBestExact)
|
||||
|| (bExact == bBestExact
|
||||
&& Entry.CellsPerAxis < TileVerdictCache[BestIndex].CellsPerAxis))
|
||||
{
|
||||
BestIndex = Index;
|
||||
bBestExact = bExact;
|
||||
}
|
||||
}
|
||||
|
||||
if (BestIndex == INDEX_NONE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FTileVerdictCacheEntry& Entry = TileVerdictCache[BestIndex];
|
||||
Entry.LastUse = TileVerdictCacheClock;
|
||||
OutVerdict = Entry.Verdict;
|
||||
bOutRegionHit = !(Entry.Origin == OriginVoxels
|
||||
&& Entry.CellsPerAxis == CellsPerAxis);
|
||||
return OutVerdict != EVoxelTileClass::Mixed;
|
||||
}
|
||||
|
||||
void UVoxelGenerator::StoreTileVerdict(
|
||||
const FIntVector& OriginVoxels, int32 Step, int32 CellsPerAxis,
|
||||
int32 ManagerWorldSeed, uint32 LayoutVersion, uint32 DiffVersion,
|
||||
uint64 ParamsFingerprint, uint8 PassageSupportFloorWrites,
|
||||
EVoxelTileClass Verdict) const
|
||||
{
|
||||
if (Verdict == EVoxelTileClass::Mixed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FScopeLock Lock(&TileVerdictCacheLock);
|
||||
++TileVerdictCacheClock;
|
||||
|
||||
int32 SlotIndex = INDEX_NONE;
|
||||
int32 VictimIndex = INDEX_NONE;
|
||||
uint32 OldestUse = MAX_uint32;
|
||||
for (int32 Index = 0; Index < TileVerdictCache.Num(); ++Index)
|
||||
{
|
||||
FTileVerdictCacheEntry& Entry = TileVerdictCache[Index];
|
||||
if (Entry.GeneratorSeed == Seed
|
||||
&& Entry.ManagerWorldSeed == ManagerWorldSeed
|
||||
&& Entry.OriginSpineRadius == OriginSpineRadius
|
||||
&& Entry.WorldRadiusVoxels == WorldRadiusVoxels
|
||||
&& Entry.EdgeSealThickness == EdgeSealThickness
|
||||
&& Entry.ManagerLifetimeId == (StrateManager ? StrateManager->GetCacheLifetimeId() : 0)
|
||||
&& Entry.LayoutVersion == LayoutVersion
|
||||
&& Entry.DiffVersion == DiffVersion
|
||||
&& Entry.ParamsFingerprint == ParamsFingerprint
|
||||
&& Entry.PassageSupportFloorWrites == PassageSupportFloorWrites
|
||||
&& Entry.Origin == OriginVoxels
|
||||
&& Entry.Step == Step
|
||||
&& Entry.CellsPerAxis == CellsPerAxis)
|
||||
{
|
||||
SlotIndex = Index;
|
||||
break;
|
||||
}
|
||||
if (Entry.LastUse < OldestUse)
|
||||
{
|
||||
OldestUse = Entry.LastUse;
|
||||
VictimIndex = Index;
|
||||
}
|
||||
}
|
||||
|
||||
if (SlotIndex == INDEX_NONE)
|
||||
{
|
||||
if (TileVerdictCache.Num() < TileVerdictCacheCapacity)
|
||||
{
|
||||
SlotIndex = TileVerdictCache.AddDefaulted();
|
||||
}
|
||||
else if (VictimIndex != INDEX_NONE)
|
||||
{
|
||||
SlotIndex = VictimIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FTileVerdictCacheEntry& Entry = TileVerdictCache[SlotIndex];
|
||||
Entry.Origin = OriginVoxels;
|
||||
Entry.Step = Step;
|
||||
Entry.CellsPerAxis = CellsPerAxis;
|
||||
Entry.GeneratorSeed = Seed;
|
||||
Entry.ManagerWorldSeed = ManagerWorldSeed;
|
||||
Entry.OriginSpineRadius = OriginSpineRadius;
|
||||
Entry.WorldRadiusVoxels = WorldRadiusVoxels;
|
||||
Entry.EdgeSealThickness = EdgeSealThickness;
|
||||
Entry.ManagerLifetimeId = StrateManager ? StrateManager->GetCacheLifetimeId() : 0;
|
||||
Entry.LayoutVersion = LayoutVersion;
|
||||
Entry.DiffVersion = DiffVersion;
|
||||
Entry.ParamsFingerprint = ParamsFingerprint;
|
||||
Entry.PassageSupportFloorWrites = PassageSupportFloorWrites;
|
||||
Entry.Verdict = Verdict;
|
||||
Entry.LastUse = TileVerdictCacheClock;
|
||||
}
|
||||
|
||||
void UVoxelGenerator::InitializeSettings(const UVoxelSettings* Settings)
|
||||
{
|
||||
// Les paramètres globaux sont copiés ici une seule fois : le chemin voxel ne doit pas
|
||||
@@ -1322,6 +1486,7 @@ void UVoxelGenerator::InitializeSettings(const UVoxelSettings* Settings)
|
||||
OriginSpineRadius = Settings ? Settings->GetEffectiveOriginSpineRadius() : 14.0f;
|
||||
WorldRadiusVoxels = Settings ? Settings->GetEffectiveWorldRadiusVoxels() : 0.0f;
|
||||
EdgeSealThickness = Settings ? Settings->EdgeSealThickness : 64.0f;
|
||||
InvalidateTileVerdictCache();
|
||||
}
|
||||
|
||||
float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) const
|
||||
@@ -4571,6 +4736,92 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels,
|
||||
EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, int32 Step,
|
||||
int32 CellsPerAxis,
|
||||
FVoxelTileClassificationStats* OutStats) const
|
||||
{
|
||||
if (OutStats)
|
||||
{
|
||||
*OutStats = FVoxelTileClassificationStats();
|
||||
}
|
||||
|
||||
const int32 NormalizedStep = FMath::Max(1, Step);
|
||||
const int32 NormalizedCellsPerAxis = FMath::Clamp(CellsPerAxis, 2, CHUNK_SIZE);
|
||||
VoxelDensityProfile::AddCounter(
|
||||
VoxelDensityProfile::ECounter::TileClassifyCalls);
|
||||
|
||||
const uint64 ParamsFingerprint = StrateManager
|
||||
? StrateManager->GetGenerationParamsFingerprint() : 0;
|
||||
const bool bCacheable = ParamsFingerprint != 0;
|
||||
const uint32 LayoutVersion = StrateManager ? StrateManager->GetLayoutVersion() : 0;
|
||||
const uint32 DiffVersion = DiffLayer ? DiffLayer->GetModsVersion() : 0;
|
||||
const int32 ManagerWorldSeed = StrateManager ? StrateManager->GetWorldSeed() : 0;
|
||||
const uint8 PassageSupportFloorWrites = StrateManager
|
||||
&& StrateManager->ArePassageSupportFloorWritesEnabledForDiagnostics() ? 1u : 0u;
|
||||
|
||||
EVoxelTileClass CachedVerdict = EVoxelTileClass::Mixed;
|
||||
bool bRegionHit = false;
|
||||
if (bCacheable
|
||||
&& FindTileVerdict(
|
||||
OriginVoxels, NormalizedStep, NormalizedCellsPerAxis,
|
||||
ManagerWorldSeed, LayoutVersion, DiffVersion, ParamsFingerprint,
|
||||
PassageSupportFloorWrites, CachedVerdict, bRegionHit))
|
||||
{
|
||||
VoxelDensityProfile::AddCounter(
|
||||
VoxelDensityProfile::ECounter::TileVerdictCacheHits);
|
||||
if (bRegionHit)
|
||||
{
|
||||
VoxelDensityProfile::AddCounter(
|
||||
VoxelDensityProfile::ECounter::TileVerdictRegionHits);
|
||||
}
|
||||
if (CachedVerdict == EVoxelTileClass::AllSolid)
|
||||
{
|
||||
VoxelDensityProfile::AddCounter(
|
||||
VoxelDensityProfile::ECounter::TileClassifyAllSolid);
|
||||
}
|
||||
else if (CachedVerdict == EVoxelTileClass::AllAir)
|
||||
{
|
||||
VoxelDensityProfile::AddCounter(
|
||||
VoxelDensityProfile::ECounter::TileClassifyAllAir);
|
||||
}
|
||||
else
|
||||
{
|
||||
VoxelDensityProfile::AddCounter(
|
||||
VoxelDensityProfile::ECounter::TileClassifyMixed);
|
||||
}
|
||||
return CachedVerdict;
|
||||
}
|
||||
|
||||
const EVoxelTileClass Verdict = ClassifyTileUncached(
|
||||
OriginVoxels, NormalizedStep, NormalizedCellsPerAxis, OutStats);
|
||||
if (Verdict == EVoxelTileClass::AllSolid)
|
||||
{
|
||||
VoxelDensityProfile::AddCounter(
|
||||
VoxelDensityProfile::ECounter::TileClassifyAllSolid);
|
||||
}
|
||||
else if (Verdict == EVoxelTileClass::AllAir)
|
||||
{
|
||||
VoxelDensityProfile::AddCounter(
|
||||
VoxelDensityProfile::ECounter::TileClassifyAllAir);
|
||||
}
|
||||
else
|
||||
{
|
||||
VoxelDensityProfile::AddCounter(
|
||||
VoxelDensityProfile::ECounter::TileClassifyMixed);
|
||||
}
|
||||
|
||||
if (bCacheable && Verdict != EVoxelTileClass::Mixed)
|
||||
{
|
||||
StoreTileVerdict(
|
||||
OriginVoxels, NormalizedStep, NormalizedCellsPerAxis,
|
||||
ManagerWorldSeed, LayoutVersion, DiffVersion, ParamsFingerprint,
|
||||
PassageSupportFloorWrites, Verdict);
|
||||
VoxelDensityProfile::AddCounter(
|
||||
VoxelDensityProfile::ECounter::TileVerdictCacheStores);
|
||||
}
|
||||
return Verdict;
|
||||
}
|
||||
|
||||
EVoxelTileClass UVoxelGenerator::ClassifyTileUncached(
|
||||
const FIntVector& OriginVoxels, int32 Step, int32 CellsPerAxis,
|
||||
FVoxelTileClassificationStats* OutStats) const
|
||||
{
|
||||
if (OutStats) { *OutStats = FVoxelTileClassificationStats(); }
|
||||
// Mêmes clamps que GenerateMesh — le verdict doit couvrir le treillis réellement échantillonné.
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "VoxelDensityProfile.h" // Opt-in targeted per-voxel attribution
|
||||
#include "VoxelTerrainOpDefinition.h" // For UVoxelTerrainOpDefinition::ApplyTo
|
||||
#include "VoxelBiomeDefinition.h" // For UVoxelBiomeDefinition (biome context flatten)
|
||||
#include "Misc/Crc.h"
|
||||
#include "UObject/UObjectGlobals.h"
|
||||
|
||||
#include <atomic>
|
||||
@@ -289,7 +290,8 @@ static bool VF_IsAnyPassageFloorAt(
|
||||
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
|
||||
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
||||
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
|
||||
|| VF_IsWalkableTunnelFloor(Passage, Position)))
|
||||
|| (Manager->ArePassageSupportFloorWritesEnabledForDiagnostics()
|
||||
&& VF_IsWalkableTunnelFloor(Passage, Position))))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -1778,7 +1780,8 @@ void UVoxelStrateManager::ApplyPassageModifier(
|
||||
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
|
||||
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
||||
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
|
||||
|| VF_IsWalkableTunnelFloor(Passage, Position)))
|
||||
|| (bPassageSupportFloorWritesEnabled
|
||||
&& VF_IsWalkableTunnelFloor(Passage, Position))))
|
||||
{
|
||||
// This is the one bidirectional part of PassageCarveOp: a floor is a proved solid
|
||||
// support slab. It is deliberately applied after the air carve so a tube can never
|
||||
@@ -1916,7 +1919,8 @@ void UVoxelStrateManager::ApplyPassageLandingFloorMC(
|
||||
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
|
||||
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
||||
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
|
||||
|| VF_IsWalkableTunnelFloor(Passage, Position)))
|
||||
|| (bPassageSupportFloorWritesEnabled
|
||||
&& VF_IsWalkableTunnelFloor(Passage, Position))))
|
||||
{
|
||||
// Result is in MC convention here (negative = solid). This reassertion is the
|
||||
// structural floor backstop after the optional MC-space disturbance layer.
|
||||
@@ -1978,7 +1982,8 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
|
||||
if (!bSuppressFloor
|
||||
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
||||
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
|
||||
|| VF_IsWalkableTunnelFloor(Passage, Position)))
|
||||
|| (bPassageSupportFloorWritesEnabled
|
||||
&& VF_IsWalkableTunnelFloor(Passage, Position))))
|
||||
{
|
||||
bAnyPassageFloor = true;
|
||||
}
|
||||
@@ -4878,3 +4883,78 @@ FStrateGenerationParams UVoxelStrateManager::BuildParamsFromDefinition(const UVo
|
||||
// Base params only — terrain op fields stay 0 until per-room assignment.
|
||||
return Definition->GenerationParams;
|
||||
}
|
||||
|
||||
uint64 UVoxelStrateManager::GetGenerationParamsFingerprint() const
|
||||
{
|
||||
// The session cache is deliberately conservative. These inputs are valid density inputs but
|
||||
// are not represented by the fixed-size hash below, so returning zero disables verdict reuse
|
||||
// instead of pretending that a partial key is complete.
|
||||
if (IsUsingSeason())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#if WITH_EDITOR
|
||||
if (ComposerOverrides.Num() > 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32 A = 0x9E3779B9u;
|
||||
uint32 B = 0x85EBCA6Bu;
|
||||
auto HashBytes = [&A, &B](const void* Data, int32 Size)
|
||||
{
|
||||
A = FCrc::MemCrc32(Data, Size, A);
|
||||
B = FCrc::MemCrc32(Data, Size, B ^ 0xA511E9B3u);
|
||||
};
|
||||
auto HashValue = [&HashBytes](const auto& Value)
|
||||
{
|
||||
HashBytes(&Value, sizeof(Value));
|
||||
};
|
||||
|
||||
HashValue(CachedSeed);
|
||||
HashValue(bOpenSurfaceEntry);
|
||||
HashValue(OriginSpineRadius);
|
||||
HashValue(InterStrateGapChunks);
|
||||
const int32 LayoutCount = StrateLayout.Num();
|
||||
HashValue(LayoutCount);
|
||||
|
||||
for (const FStrateSlot& Slot : StrateLayout)
|
||||
{
|
||||
if (Slot.Definition == nullptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// BuildChunkCache resolves these arrays into the room cache. Hashing UObject pointers or
|
||||
// asset names would not prove the asset contents, so dynamic operation/biome layouts
|
||||
// conservatively opt out. The normal synthetic/runtime profile has neither array.
|
||||
if (Slot.Definition->TerrainOperations.Num() > 0
|
||||
|| Slot.Definition->Biomes.Num() > 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
HashValue(Slot.StrateIndex);
|
||||
HashValue(Slot.TopChunkZ);
|
||||
HashValue(Slot.BottomChunkZ);
|
||||
HashValue(Slot.HeightInChunks);
|
||||
HashValue(Slot.Definition->GeneratorType);
|
||||
HashValue(Slot.Definition->bUseOperatorStack);
|
||||
HashValue(Slot.Definition->StrateHeightInChunks);
|
||||
HashValue(Slot.Definition->TransitionType);
|
||||
HashValue(Slot.Definition->TransitionBlendChunks);
|
||||
HashValue(Slot.Definition->GenerationParams);
|
||||
HashValue(Slot.Definition->SlabParams);
|
||||
HashValue(Slot.Definition->MazeParams);
|
||||
HashValue(Slot.Definition->SurfaceParams);
|
||||
HashValue(Slot.Definition->VerticalShaftParams);
|
||||
HashValue(Slot.Definition->FloatingIslandParams);
|
||||
HashValue(Slot.Definition->PassageConfig);
|
||||
HashValue(Slot.Definition->bHasWater);
|
||||
HashValue(Slot.Definition->BiomeMapParams);
|
||||
}
|
||||
|
||||
const uint64 Result = (static_cast<uint64>(B) << 32) | static_cast<uint64>(A);
|
||||
return Result != 0 ? Result : 1;
|
||||
}
|
||||
@@ -556,6 +556,16 @@ struct FCachedPinch
|
||||
float BaseDensity = 0.0f;
|
||||
};
|
||||
|
||||
// A build-time room-floor bench authored from one tunnel mouth. The room evaluator only reads
|
||||
// these immutable descriptors; it never searches the graph or decides a correction per sample.
|
||||
struct FCachedRoomMouthRise
|
||||
{
|
||||
FVector Mouth = FVector::ZeroVector;
|
||||
float TargetFloorZ = 0.0f;
|
||||
float BlendRadius = 8.0f;
|
||||
float Strength = 0.0f;
|
||||
};
|
||||
|
||||
// A room in the cache — everything needed for per-voxel SDF evaluation.
|
||||
// Internal details (CellX, CellY) used during cache building are NOT stored here.
|
||||
struct FCachedRoom
|
||||
@@ -579,6 +589,10 @@ struct FCachedRoom
|
||||
float FloorReliefFrequency;
|
||||
uint32 FloorSeed; // Deterministic seed offset for this room's floor noise
|
||||
|
||||
// Optional build-time floor benches leading to higher tunnel mouths. Empty in the default
|
||||
// compatibility profile, so the baseline field is unchanged until an archetype opts in.
|
||||
TArray<FCachedRoomMouthRise> MouthRises;
|
||||
|
||||
// Per-room terrain operation, hash-rolled during BuildChunkCache from the
|
||||
// strate's probability pool (FStrateTerrainOpEntry::Probability).
|
||||
// null = this room has no terrain op (the "no op" slot in the probability pool).
|
||||
|
||||
@@ -114,6 +114,13 @@ namespace VoxelDensityProfile
|
||||
MesherBlockTests,
|
||||
MesherBlockAllSolid,
|
||||
MesherBlockAllAir,
|
||||
TileClassifyCalls,
|
||||
TileClassifyAllSolid,
|
||||
TileClassifyAllAir,
|
||||
TileClassifyMixed,
|
||||
TileVerdictCacheHits,
|
||||
TileVerdictRegionHits,
|
||||
TileVerdictCacheStores,
|
||||
Count
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "HAL/CriticalSection.h"
|
||||
#include "VoxelTypes.h"
|
||||
#include "VoxelStrateTypes.h"
|
||||
#include "VoxelBiomeTypes.h"
|
||||
@@ -114,8 +115,16 @@ public:
|
||||
UPROPERTY()
|
||||
const UVoxelDiffLayer* DiffLayer = nullptr;
|
||||
|
||||
void SetStrateManager(const UVoxelStrateManager* InManager) { StrateManager = InManager; }
|
||||
void SetDiffLayer(const UVoxelDiffLayer* InDiffLayer) { DiffLayer = InDiffLayer; }
|
||||
void SetStrateManager(const UVoxelStrateManager* InManager)
|
||||
{
|
||||
StrateManager = InManager;
|
||||
InvalidateTileVerdictCache();
|
||||
}
|
||||
void SetDiffLayer(const UVoxelDiffLayer* InDiffLayer)
|
||||
{
|
||||
DiffLayer = InDiffLayer;
|
||||
InvalidateTileVerdictCache();
|
||||
}
|
||||
|
||||
// Copie le seed depuis les settings. Appelé au démarrage et lors d'un ChangeSeed.
|
||||
void InitializeSettings(const UVoxelSettings* Settings);
|
||||
@@ -351,6 +360,57 @@ private:
|
||||
* Process-unique owner identity for the `CP_*` thread-local cache key. */
|
||||
uint64 DensityCacheOwnerId = 0;
|
||||
|
||||
/**
|
||||
* A bounded, session-resident proof cache for uniform MC tiles.
|
||||
*
|
||||
* This is deliberately not a disk cache: a verdict is only useful while the exact generator
|
||||
* and its immutable layout are alive, and a stale "all air" verdict would be a hole. The key
|
||||
* carries every runtime invalidator that can change the sampled field. Entries are only
|
||||
* stored for AllSolid/AllAir; Mixed is never remembered because it is not a proof.
|
||||
*
|
||||
* A larger proven tile is also a proof for an aligned sub-tile. FindTileVerdict therefore
|
||||
* accepts an enclosing entry and records that separately from an exact revisit. Capacity is
|
||||
* fixed so repeated exploration cannot grow memory without bound.
|
||||
*/
|
||||
struct FTileVerdictCacheEntry
|
||||
{
|
||||
FIntVector Origin = FIntVector::ZeroValue;
|
||||
int32 Step = 1;
|
||||
int32 CellsPerAxis = 0;
|
||||
int32 GeneratorSeed = 0;
|
||||
int32 ManagerWorldSeed = 0;
|
||||
float OriginSpineRadius = 0.0f;
|
||||
float WorldRadiusVoxels = 0.0f;
|
||||
float EdgeSealThickness = 0.0f;
|
||||
uint64 ManagerLifetimeId = 0;
|
||||
uint32 LayoutVersion = 0;
|
||||
uint32 DiffVersion = 0;
|
||||
uint64 ParamsFingerprint = 0;
|
||||
uint8 PassageSupportFloorWrites = 1;
|
||||
EVoxelTileClass Verdict = EVoxelTileClass::Mixed;
|
||||
uint32 LastUse = 0;
|
||||
};
|
||||
|
||||
static constexpr int32 TileVerdictCacheCapacity = 512;
|
||||
mutable FCriticalSection TileVerdictCacheLock;
|
||||
mutable TArray<FTileVerdictCacheEntry> TileVerdictCache;
|
||||
mutable uint32 TileVerdictCacheClock = 0;
|
||||
|
||||
void InvalidateTileVerdictCache() const;
|
||||
bool FindTileVerdict(
|
||||
const FIntVector& OriginVoxels, int32 Step, int32 CellsPerAxis,
|
||||
int32 ManagerWorldSeed, uint32 LayoutVersion, uint32 DiffVersion,
|
||||
uint64 ParamsFingerprint, uint8 PassageSupportFloorWrites,
|
||||
EVoxelTileClass& OutVerdict, bool& bOutRegionHit) const;
|
||||
void StoreTileVerdict(
|
||||
const FIntVector& OriginVoxels, int32 Step, int32 CellsPerAxis,
|
||||
int32 ManagerWorldSeed, uint32 LayoutVersion, uint32 DiffVersion,
|
||||
uint64 ParamsFingerprint, uint8 PassageSupportFloorWrites,
|
||||
EVoxelTileClass Verdict) const;
|
||||
EVoxelTileClass ClassifyTileUncached(
|
||||
const FIntVector& OriginVoxels, int32 Step, int32 CellsPerAxis,
|
||||
struct FVoxelTileClassificationStats* OutStats) const;
|
||||
|
||||
/** Pick the biome (index into Ctx.Biomes) for a Voronoi site, by its climate. */
|
||||
int32 ClassifyBiomeAtSite(float SiteX, float SiteY, const FBiomeContext& Ctx, uint32 SiteHash) const;
|
||||
|
||||
|
||||
@@ -240,6 +240,26 @@ public:
|
||||
* live edit) can never serve a stale strate index or passage shortlist. */
|
||||
uint32 GetLayoutVersion() const { return PassagesVersion; }
|
||||
|
||||
/**
|
||||
* Fingerprint of the immutable density inputs used by the generator.
|
||||
*
|
||||
* Returns zero when the manager has dynamic inputs that this compact cache key intentionally
|
||||
* does not serialize (seasons, composer recipes, biome maps, or terrain-op assets). Zero is a
|
||||
* fail-closed answer: callers must skip verdict reuse rather than risk an old all-air proof.
|
||||
*/
|
||||
uint64 GetGenerationParamsFingerprint() const;
|
||||
|
||||
/** Diagnostic-only switch for isolating the inter-strate support writer. */
|
||||
void SetPassageSupportFloorWritesEnabledForDiagnostics(bool bEnabled)
|
||||
{
|
||||
bPassageSupportFloorWritesEnabled = bEnabled;
|
||||
}
|
||||
|
||||
bool ArePassageSupportFloorWritesEnabledForDiagnostics() const
|
||||
{
|
||||
return bPassageSupportFloorWritesEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the strate definition for a specific chunk coordinate.
|
||||
*
|
||||
@@ -499,6 +519,9 @@ protected:
|
||||
// VoxelSettings::InterStrateGapChunks during Initialize().
|
||||
int32 InterStrateGapChunks = 0;
|
||||
|
||||
// Production is true. This diagnostic switch never changes the passage carve or air contract.
|
||||
bool bPassageSupportFloorWritesEnabled = true;
|
||||
|
||||
// Per-passage tunnel shape now lives on each UVoxelStrateDefinition::PassageConfig
|
||||
// (the upper strate of each boundary controls its own descent tunnels).
|
||||
|
||||
|
||||
@@ -272,6 +272,8 @@ enum class EVoxelStrateTransition : uint8
|
||||
LERPF(RoomFloorCutMax) \
|
||||
LERPF(FloorReliefStrength) \
|
||||
LERPF(FloorReliefFrequency) \
|
||||
LERPF(RoomMouthRiseStrength) \
|
||||
LERPF(RoomMouthRiseBlendVoxels) \
|
||||
LERPF(OriginRoomRadius) \
|
||||
SNAPF(OriginRoomMaxConnections) \
|
||||
/* Cave morphology — tunnels */ \
|
||||
@@ -559,6 +561,17 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
meta = (ClampMin = "0.001", EditCondition = "RoomFloorCutMin < 1.0"))
|
||||
float FloorReliefFrequency = 0.015f;
|
||||
|
||||
// Optional local room-floor bench that rises toward a higher tunnel mouth. It is authored
|
||||
// from the build-time tunnel descriptor, not discovered by a post-generation validator.
|
||||
// Strength 0 keeps the legacy room/tunnel hand-off; 1 reaches the compatible mouth floor.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Rooms|Mouth Blend",
|
||||
meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float RoomMouthRiseStrength = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Rooms|Mouth Blend",
|
||||
meta = (ClampMin = "8.0", ClampMax = "128.0"))
|
||||
float RoomMouthRiseBlendVoxels = 8.0f;
|
||||
|
||||
// How much variety in room shapes (0 = all ellipsoids, 1 = full variety).
|
||||
// At 0: every room is a smooth ellipsoid (uniform, organic caves).
|
||||
// At 0.5: some rooms become angular (rounded boxes) or elongated (capsules).
|
||||
|
||||
@@ -147,6 +147,69 @@ bool ParseArchetype(const FString& Text, ECaveGeneratorType& OutArchetype)
|
||||
return false;
|
||||
}
|
||||
|
||||
const TCHAR* NoiseTypeName(EVoxelNoiseType NoiseType)
|
||||
{
|
||||
switch (NoiseType)
|
||||
{
|
||||
case EVoxelNoiseType::FBM: return TEXT("FBM");
|
||||
case EVoxelNoiseType::Ridged: return TEXT("Ridged");
|
||||
case EVoxelNoiseType::Mixed: return TEXT("Mixed");
|
||||
case EVoxelNoiseType::Cellular: return TEXT("Cellular");
|
||||
default: return TEXT("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
bool ParseNoiseType(const FString& Text, EVoxelNoiseType& OutNoiseType)
|
||||
{
|
||||
FString Normalized = Text;
|
||||
Normalized.TrimStartAndEndInline();
|
||||
Normalized.ToLowerInline();
|
||||
Normalized.ReplaceInline(TEXT("_"), TEXT(""));
|
||||
Normalized.ReplaceInline(TEXT("-"), TEXT(""));
|
||||
|
||||
if (Normalized == TEXT("fbm") || Normalized == TEXT("fractal"))
|
||||
{
|
||||
OutNoiseType = EVoxelNoiseType::FBM;
|
||||
return true;
|
||||
}
|
||||
if (Normalized == TEXT("ridged") || Normalized == TEXT("ridge"))
|
||||
{
|
||||
OutNoiseType = EVoxelNoiseType::Ridged;
|
||||
return true;
|
||||
}
|
||||
if (Normalized == TEXT("mixed"))
|
||||
{
|
||||
OutNoiseType = EVoxelNoiseType::Mixed;
|
||||
return true;
|
||||
}
|
||||
if (Normalized == TEXT("cellular") || Normalized == TEXT("worley"))
|
||||
{
|
||||
OutNoiseType = EVoxelNoiseType::Cellular;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParseRenderCamera(const FString& Text, FVector& OutCamera)
|
||||
{
|
||||
TArray<FString> Components;
|
||||
Text.ParseIntoArray(Components, TEXT(","), true);
|
||||
if (Components.Num() != 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const float X = FCString::Atof(*Components[0]);
|
||||
const float Y = FCString::Atof(*Components[1]);
|
||||
const float Z = FCString::Atof(*Components[2]);
|
||||
if (!FMath::IsFinite(X) || !FMath::IsFinite(Y) || !FMath::IsFinite(Z))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
OutCamera = FVector(X, Y, Z);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NeedsOriginInWindow(ECaveGeneratorType Archetype)
|
||||
{
|
||||
// This is the same topology policy as VoxelForgePlayerFitWindow.h. That header is test-only,
|
||||
@@ -205,6 +268,7 @@ struct FExploreArguments
|
||||
bool bProfileDensityFull = false;
|
||||
bool bProfileLod = false;
|
||||
bool bOpBounds = false;
|
||||
bool bProfileTileVerdictCache = false;
|
||||
bool bFailureFocusRender = false;
|
||||
FString OutDirectory;
|
||||
|
||||
@@ -223,6 +287,10 @@ struct FExploreArguments
|
||||
float BudgetMinutes = DefaultBudgetMinutes;
|
||||
bool bSurfaceRoughnessOverride = false;
|
||||
float SurfaceRoughness = 0.0f;
|
||||
bool bRoughnessNoiseTypeOverride = false;
|
||||
EVoxelNoiseType RoughnessNoiseType = EVoxelNoiseType::FBM;
|
||||
bool bRoughnessFrequencyOverride = false;
|
||||
float RoughnessFrequency = 0.0f;
|
||||
bool bTunnelFloorEnabledOverride = false;
|
||||
int32 TunnelFloorEnabled = 1;
|
||||
bool bTunnelFloorTerracingOverride = false;
|
||||
@@ -237,6 +305,14 @@ struct FExploreArguments
|
||||
int32 TunnelFloorLedges = 0;
|
||||
bool bTunnelFloorMaxLedgesOverride = false;
|
||||
int32 TunnelFloorMaxLedges = 0;
|
||||
bool bRoomMouthRiseOverride = false;
|
||||
float RoomMouthRise = 0.0f;
|
||||
bool bRoomMouthRiseBlendOverride = false;
|
||||
float RoomMouthRiseBlend = 0.0f;
|
||||
bool bPassageFloorWritesOverride = false;
|
||||
int32 PassageFloorWrites = 1;
|
||||
bool bRenderCameraOverride = false;
|
||||
FVector RenderCamera = FVector::ZeroVector;
|
||||
|
||||
FString CanonicalModes() const
|
||||
{
|
||||
@@ -285,6 +361,8 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
|
||||
OutArguments.bProfileDensity |= OutArguments.bProfileDensityFull;
|
||||
OutArguments.bProfileLod = FParse::Param(*Params, TEXT("profilelod"));
|
||||
OutArguments.bOpBounds = FParse::Param(*Params, TEXT("opbounds"));
|
||||
OutArguments.bProfileTileVerdictCache = FParse::Param(
|
||||
*Params, TEXT("profiletileverdictcache"));
|
||||
FParse::Value(*Params, TEXT("out="), OutText);
|
||||
FParse::Value(*Params, TEXT("renderwidth="), OutArguments.RenderWidth);
|
||||
FParse::Value(*Params, TEXT("renderheight="), OutArguments.RenderHeight);
|
||||
@@ -317,6 +395,18 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
|
||||
const bool bSurfaceRoughnessParsed = FParse::Value(
|
||||
*Params, TEXT("surfaceroughness="), OutArguments.SurfaceRoughness);
|
||||
OutArguments.bSurfaceRoughnessOverride = bSurfaceRoughnessSpecified;
|
||||
FString RoughnessNoiseTypeText;
|
||||
const bool bRoughnessNoiseTypeSpecified = Params.Contains(
|
||||
TEXT("roughnesstype="), ESearchCase::IgnoreCase);
|
||||
const bool bRoughnessNoiseTypeParsed = FParse::Value(
|
||||
*Params, TEXT("roughnesstype="), RoughnessNoiseTypeText)
|
||||
&& ParseNoiseType(RoughnessNoiseTypeText, OutArguments.RoughnessNoiseType);
|
||||
OutArguments.bRoughnessNoiseTypeOverride = bRoughnessNoiseTypeSpecified;
|
||||
const bool bRoughnessFrequencySpecified = Params.Contains(
|
||||
TEXT("roughnessfrequency="), ESearchCase::IgnoreCase);
|
||||
const bool bRoughnessFrequencyParsed = FParse::Value(
|
||||
*Params, TEXT("roughnessfrequency="), OutArguments.RoughnessFrequency);
|
||||
OutArguments.bRoughnessFrequencyOverride = bRoughnessFrequencySpecified;
|
||||
const bool bTunnelFloorEnabledSpecified = Params.Contains(
|
||||
TEXT("tunnelfloor="), ESearchCase::IgnoreCase);
|
||||
const bool bTunnelFloorEnabledParsed = FParse::Value(
|
||||
@@ -352,6 +442,42 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
|
||||
const bool bTunnelFloorMaxLedgesParsed = FParse::Value(
|
||||
*Params, TEXT("tunnelfloormaxledges="), OutArguments.TunnelFloorMaxLedges);
|
||||
OutArguments.bTunnelFloorMaxLedgesOverride = bTunnelFloorMaxLedgesSpecified;
|
||||
const bool bRoomMouthRiseSpecified = Params.Contains(
|
||||
TEXT("roommouthrise="), ESearchCase::IgnoreCase);
|
||||
const bool bRoomMouthRiseParsed = FParse::Value(
|
||||
*Params, TEXT("roommouthrise="), OutArguments.RoomMouthRise);
|
||||
OutArguments.bRoomMouthRiseOverride = bRoomMouthRiseSpecified;
|
||||
const bool bRoomMouthRiseBlendSpecified = Params.Contains(
|
||||
TEXT("roommouthriseblend="), ESearchCase::IgnoreCase);
|
||||
const bool bRoomMouthRiseBlendParsed = FParse::Value(
|
||||
*Params, TEXT("roommouthriseblend="), OutArguments.RoomMouthRiseBlend);
|
||||
OutArguments.bRoomMouthRiseBlendOverride = bRoomMouthRiseBlendSpecified;
|
||||
const bool bPassageFloorWritesSpecified = Params.Contains(
|
||||
TEXT("passagefloorwrites="), ESearchCase::IgnoreCase);
|
||||
const bool bPassageFloorWritesParsed = FParse::Value(
|
||||
*Params, TEXT("passagefloorwrites="), OutArguments.PassageFloorWrites);
|
||||
OutArguments.bPassageFloorWritesOverride = bPassageFloorWritesSpecified;
|
||||
|
||||
FString RenderCameraText;
|
||||
const bool bRenderCameraSpecified = Params.Contains(
|
||||
TEXT("rendercamera="), ESearchCase::IgnoreCase);
|
||||
bool bRenderCameraParsed = false;
|
||||
if (bRenderCameraSpecified)
|
||||
{
|
||||
const int32 CameraOffset = Params.Find(TEXT("rendercamera="), ESearchCase::IgnoreCase);
|
||||
if (CameraOffset != INDEX_NONE)
|
||||
{
|
||||
RenderCameraText = Params.Mid(CameraOffset + 13);
|
||||
const int32 CameraEnd = RenderCameraText.Find(TEXT(" "));
|
||||
if (CameraEnd != INDEX_NONE)
|
||||
{
|
||||
RenderCameraText.LeftInline(CameraEnd);
|
||||
}
|
||||
RenderCameraText.TrimQuotesInline();
|
||||
bRenderCameraParsed = ParseRenderCamera(RenderCameraText, OutArguments.RenderCamera);
|
||||
}
|
||||
}
|
||||
OutArguments.bRenderCameraOverride = bRenderCameraSpecified;
|
||||
int32 FailureFocusRender = 0;
|
||||
FParse::Value(*Params, TEXT("failurefocus="), FailureFocusRender);
|
||||
OutArguments.bFailureFocusRender = FailureFocusRender != 0;
|
||||
@@ -488,6 +614,19 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
|
||||
OutError = TEXT("surfaceroughness must be finite and greater than or equal to zero.");
|
||||
return false;
|
||||
}
|
||||
if (OutArguments.bRoughnessNoiseTypeOverride && !bRoughnessNoiseTypeParsed)
|
||||
{
|
||||
OutError = TEXT("roughnesstype must be FBM, Ridged, Mixed, or Cellular.");
|
||||
return false;
|
||||
}
|
||||
if (OutArguments.bRoughnessFrequencyOverride
|
||||
&& (!bRoughnessFrequencyParsed
|
||||
|| !FMath::IsFinite(OutArguments.RoughnessFrequency)
|
||||
|| OutArguments.RoughnessFrequency <= 0.0f))
|
||||
{
|
||||
OutError = TEXT("roughnessfrequency must be finite and greater than zero.");
|
||||
return false;
|
||||
}
|
||||
if (OutArguments.bTunnelFloorEnabledOverride
|
||||
&& (!bTunnelFloorEnabledParsed
|
||||
|| (OutArguments.TunnelFloorEnabled != 0
|
||||
@@ -544,6 +683,37 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
|
||||
OutError = TEXT("tunnelfloormaxledges must be an integer in [1,4096].");
|
||||
return false;
|
||||
}
|
||||
if (OutArguments.bRoomMouthRiseOverride
|
||||
&& (!bRoomMouthRiseParsed
|
||||
|| !FMath::IsFinite(OutArguments.RoomMouthRise)
|
||||
|| OutArguments.RoomMouthRise < 0.0f
|
||||
|| OutArguments.RoomMouthRise > 1.0f))
|
||||
{
|
||||
OutError = TEXT("roommouthrise must be finite and in [0,1].");
|
||||
return false;
|
||||
}
|
||||
if (OutArguments.bRoomMouthRiseBlendOverride
|
||||
&& (!bRoomMouthRiseBlendParsed
|
||||
|| !FMath::IsFinite(OutArguments.RoomMouthRiseBlend)
|
||||
|| OutArguments.RoomMouthRiseBlend < 8.0f
|
||||
|| OutArguments.RoomMouthRiseBlend > 128.0f))
|
||||
{
|
||||
OutError = TEXT("roommouthriseblend must be finite and in [8,128].");
|
||||
return false;
|
||||
}
|
||||
if (OutArguments.bPassageFloorWritesOverride
|
||||
&& (!bPassageFloorWritesParsed
|
||||
|| (OutArguments.PassageFloorWrites != 0
|
||||
&& OutArguments.PassageFloorWrites != 1)))
|
||||
{
|
||||
OutError = TEXT("passagefloorwrites must be 0 or 1.");
|
||||
return false;
|
||||
}
|
||||
if (OutArguments.bRenderCameraOverride && !bRenderCameraParsed)
|
||||
{
|
||||
OutError = TEXT("rendercamera must be three finite comma-separated voxel coordinates.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (OutText.IsEmpty())
|
||||
{
|
||||
@@ -773,6 +943,11 @@ struct FExploreWorld
|
||||
OutError = TEXT("UVoxelStrateManager::Initialize refused the transient layout.");
|
||||
return false;
|
||||
}
|
||||
if (Arguments.bPassageFloorWritesOverride)
|
||||
{
|
||||
Manager->SetPassageSupportFloorWritesEnabledForDiagnostics(
|
||||
Arguments.PassageFloorWrites != 0);
|
||||
}
|
||||
|
||||
if (!Manager->GetLayout().IsValidIndex(Arguments.Slot))
|
||||
{
|
||||
@@ -816,6 +991,27 @@ struct FExploreWorld
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Arguments.bRoughnessNoiseTypeOverride
|
||||
|| Arguments.bRoughnessFrequencyOverride)
|
||||
{
|
||||
if (Arguments.Archetype != ECaveGeneratorType::TunnelNetwork
|
||||
&& Arguments.Archetype != ECaveGeneratorType::Underwater)
|
||||
{
|
||||
OutError = TEXT(
|
||||
"roughnesstype/roughnessfrequency are only supported by TunnelNetwork or Underwater.");
|
||||
return false;
|
||||
}
|
||||
if (Arguments.bRoughnessNoiseTypeOverride)
|
||||
{
|
||||
Target.Definition->GenerationParams.RoughnessNoiseType =
|
||||
Arguments.RoughnessNoiseType;
|
||||
}
|
||||
if (Arguments.bRoughnessFrequencyOverride)
|
||||
{
|
||||
Target.Definition->GenerationParams.RoughnessFrequency =
|
||||
Arguments.RoughnessFrequency;
|
||||
}
|
||||
}
|
||||
// Floor controls are applied only to the requested target definition. They are
|
||||
// commandlet overrides for measured archetype comparisons; authored assets still carry
|
||||
// the defaults/rolled values in FStrateGenerationParams.
|
||||
@@ -850,6 +1046,14 @@ struct FExploreWorld
|
||||
{
|
||||
TargetTunnelParams.TunnelFloorMaxLedges = Arguments.TunnelFloorMaxLedges;
|
||||
}
|
||||
if (Arguments.bRoomMouthRiseOverride)
|
||||
{
|
||||
TargetTunnelParams.RoomMouthRiseStrength = Arguments.RoomMouthRise;
|
||||
}
|
||||
if (Arguments.bRoomMouthRiseBlendOverride)
|
||||
{
|
||||
TargetTunnelParams.RoomMouthRiseBlendVoxels = Arguments.RoomMouthRiseBlend;
|
||||
}
|
||||
TargetBottomWorldZ = Target.BottomChunkZ * CHUNK_SIZE;
|
||||
TargetTopWorldZ = (Target.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
TargetBoundarySealThickness = BoundarySealForArchetype(
|
||||
@@ -2388,6 +2592,11 @@ bool RunRender(
|
||||
CameraSeeds.Emplace(Position, FString(Source));
|
||||
};
|
||||
|
||||
if (Arguments.bRenderCameraOverride)
|
||||
{
|
||||
AddCameraSeed(true, Arguments.RenderCamera, TEXT("render_camera_override"));
|
||||
}
|
||||
|
||||
for (int32 SeedIndex = 0; SeedIndex < CameraSeedWalk.CameraSeedPoses.Num(); ++SeedIndex)
|
||||
{
|
||||
const FString Source = CameraSeedWalk.CameraSeedSources.IsValidIndex(SeedIndex)
|
||||
@@ -3797,6 +4006,13 @@ void WriteTunnelFloorOverrides(
|
||||
{
|
||||
Writer.WriteValue(TEXT("max_ledges"), Arguments.TunnelFloorMaxLedges);
|
||||
}
|
||||
Writer.WriteValue(TEXT("passage_support_floor_writes_override"),
|
||||
Arguments.bPassageFloorWritesOverride);
|
||||
if (Arguments.bPassageFloorWritesOverride)
|
||||
{
|
||||
Writer.WriteValue(TEXT("passage_support_floor_writes"),
|
||||
Arguments.PassageFloorWrites != 0);
|
||||
}
|
||||
Writer.WriteObjectEnd();
|
||||
}
|
||||
|
||||
@@ -3836,12 +4052,44 @@ FString BuildManifestJson(
|
||||
Writer->WriteValue(TEXT("slot"), Arguments.Slot);
|
||||
Writer->WriteValue(TEXT("operator_stack"), Arguments.bUseOperatorStack);
|
||||
WriteTunnelFloorOverrides(*Writer, Arguments);
|
||||
Writer->WriteValue(TEXT("room_mouth_rise_override"), Arguments.bRoomMouthRiseOverride);
|
||||
if (Arguments.bRoomMouthRiseOverride)
|
||||
{
|
||||
Writer->WriteValue(TEXT("room_mouth_rise_strength"),
|
||||
static_cast<double>(Arguments.RoomMouthRise));
|
||||
}
|
||||
Writer->WriteValue(TEXT("room_mouth_rise_blend_override"),
|
||||
Arguments.bRoomMouthRiseBlendOverride);
|
||||
if (Arguments.bRoomMouthRiseBlendOverride)
|
||||
{
|
||||
Writer->WriteValue(TEXT("room_mouth_rise_blend_voxels"),
|
||||
static_cast<double>(Arguments.RoomMouthRiseBlend));
|
||||
}
|
||||
Writer->WriteValue(TEXT("surface_roughness_override"), Arguments.bSurfaceRoughnessOverride);
|
||||
if (Arguments.bSurfaceRoughnessOverride)
|
||||
{
|
||||
Writer->WriteValue(TEXT("surface_roughness"),
|
||||
static_cast<double>(Arguments.SurfaceRoughness));
|
||||
}
|
||||
Writer->WriteValue(TEXT("roughness_noise_type_override"),
|
||||
Arguments.bRoughnessNoiseTypeOverride);
|
||||
if (Arguments.bRoughnessNoiseTypeOverride)
|
||||
{
|
||||
Writer->WriteValue(TEXT("roughness_noise_type"),
|
||||
NoiseTypeName(Arguments.RoughnessNoiseType));
|
||||
}
|
||||
Writer->WriteValue(TEXT("roughness_frequency_override"),
|
||||
Arguments.bRoughnessFrequencyOverride);
|
||||
if (Arguments.bRoughnessFrequencyOverride)
|
||||
{
|
||||
Writer->WriteValue(TEXT("roughness_frequency"),
|
||||
static_cast<double>(Arguments.RoughnessFrequency));
|
||||
}
|
||||
Writer->WriteValue(TEXT("render_camera_override"), Arguments.bRenderCameraOverride);
|
||||
if (Arguments.bRenderCameraOverride)
|
||||
{
|
||||
WriteJsonVector(*Writer, TEXT("render_camera_voxels"), Arguments.RenderCamera, 1.0f);
|
||||
}
|
||||
WriteJsonIntVector(*Writer, TEXT("region_origin_voxels"), Export.RegionOrigin);
|
||||
WriteJsonBounds(*Writer, TEXT("region_bounds_voxels"), Export.RegionOrigin, Export.RegionSize, 0.25f);
|
||||
Writer->WriteObjectStart(TEXT("region_bounds_metres"));
|
||||
@@ -4055,12 +4303,39 @@ FString BuildExploreJson(
|
||||
Writer->WriteValue(TEXT("slot"), Arguments.Slot);
|
||||
Writer->WriteValue(TEXT("operator_stack"), Arguments.bUseOperatorStack);
|
||||
WriteTunnelFloorOverrides(*Writer, Arguments);
|
||||
Writer->WriteValue(TEXT("room_mouth_rise_override"), Arguments.bRoomMouthRiseOverride);
|
||||
if (Arguments.bRoomMouthRiseOverride)
|
||||
{
|
||||
Writer->WriteValue(TEXT("room_mouth_rise_strength"),
|
||||
static_cast<double>(Arguments.RoomMouthRise));
|
||||
}
|
||||
Writer->WriteValue(TEXT("room_mouth_rise_blend_override"),
|
||||
Arguments.bRoomMouthRiseBlendOverride);
|
||||
if (Arguments.bRoomMouthRiseBlendOverride)
|
||||
{
|
||||
Writer->WriteValue(TEXT("room_mouth_rise_blend_voxels"),
|
||||
static_cast<double>(Arguments.RoomMouthRiseBlend));
|
||||
}
|
||||
Writer->WriteValue(TEXT("surface_roughness_override"), Arguments.bSurfaceRoughnessOverride);
|
||||
if (Arguments.bSurfaceRoughnessOverride)
|
||||
{
|
||||
Writer->WriteValue(TEXT("surface_roughness"),
|
||||
static_cast<double>(Arguments.SurfaceRoughness));
|
||||
}
|
||||
Writer->WriteValue(TEXT("roughness_noise_type_override"),
|
||||
Arguments.bRoughnessNoiseTypeOverride);
|
||||
if (Arguments.bRoughnessNoiseTypeOverride)
|
||||
{
|
||||
Writer->WriteValue(TEXT("roughness_noise_type"),
|
||||
NoiseTypeName(Arguments.RoughnessNoiseType));
|
||||
}
|
||||
Writer->WriteValue(TEXT("roughness_frequency_override"),
|
||||
Arguments.bRoughnessFrequencyOverride);
|
||||
if (Arguments.bRoughnessFrequencyOverride)
|
||||
{
|
||||
Writer->WriteValue(TEXT("roughness_frequency"),
|
||||
static_cast<double>(Arguments.RoughnessFrequency));
|
||||
}
|
||||
Writer->WriteArrayStart(TEXT("modes"));
|
||||
if (Arguments.bRender) Writer->WriteValue(TEXT("render"));
|
||||
if (Arguments.bWalk) Writer->WriteValue(TEXT("walk"));
|
||||
@@ -4077,6 +4352,8 @@ FString BuildExploreJson(
|
||||
Writer->WriteValue(TEXT("profile_density"), Arguments.bProfileDensity);
|
||||
Writer->WriteValue(TEXT("profile_density_full"), Arguments.bProfileDensityFull);
|
||||
Writer->WriteValue(TEXT("profile_lod"), Arguments.bProfileLod);
|
||||
Writer->WriteValue(TEXT("profile_tile_verdict_cache"),
|
||||
Arguments.bProfileTileVerdictCache);
|
||||
Writer->WriteValue(TEXT("op_bounds"), Arguments.bOpBounds);
|
||||
Writer->WriteValue(TEXT("budget_minutes"), static_cast<double>(Arguments.BudgetMinutes));
|
||||
Writer->WriteValue(TEXT("out_directory"), Arguments.OutDirectory);
|
||||
@@ -5133,6 +5410,66 @@ bool RunOpBounds(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RunTileVerdictCacheProbe(
|
||||
const FExploreArguments& Arguments,
|
||||
FExploreWorld& World,
|
||||
FString& OutError)
|
||||
{
|
||||
if (!World.Generator)
|
||||
{
|
||||
OutError = TEXT("tile-verdict cache probe has no generator.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Deliberately use a region well above the synthetic layout. The first 32-cell proof is a
|
||||
// genuine enclosing-region certificate; the 8-cell requests below are aligned subregions of
|
||||
// that same lattice. No generated cave data is involved in this probe.
|
||||
const FIntVector RegionOrigin(-32, -32, World.TargetTopWorldZ + 1024);
|
||||
const EVoxelTileClass RegionVerdict = World.Generator->ClassifyTile(
|
||||
RegionOrigin, 1, 32);
|
||||
if (RegionVerdict != EVoxelTileClass::AllAir)
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("tile-verdict cache probe expected an all-air enclosing region, got %d."),
|
||||
static_cast<int32>(RegionVerdict));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ClassifyChildren = [&World, &RegionOrigin]()
|
||||
{
|
||||
for (int32 Z = 0; Z < 32; Z += 8)
|
||||
for (int32 Y = 0; Y < 32; Y += 8)
|
||||
for (int32 X = 0; X < 32; X += 8)
|
||||
{
|
||||
World.Generator->ClassifyTile(
|
||||
RegionOrigin + FIntVector(X, Y, Z), 1, 8);
|
||||
}
|
||||
};
|
||||
|
||||
ClassifyChildren();
|
||||
World.Generator->ClassifyTile(RegionOrigin, 1, 32);
|
||||
ClassifyChildren();
|
||||
|
||||
const VoxelDensityProfile::FSnapshot Snapshot = VoxelDensityProfile::Snapshot();
|
||||
const int32 CallsIndex = static_cast<int32>(
|
||||
VoxelDensityProfile::ECounter::TileClassifyCalls);
|
||||
const int32 HitsIndex = static_cast<int32>(
|
||||
VoxelDensityProfile::ECounter::TileVerdictCacheHits);
|
||||
const int32 RegionHitsIndex = static_cast<int32>(
|
||||
VoxelDensityProfile::ECounter::TileVerdictRegionHits);
|
||||
const int32 StoresIndex = static_cast<int32>(
|
||||
VoxelDensityProfile::ECounter::TileVerdictCacheStores);
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTileVerdictCache] probe region=(%d,%d,%d) cells=32 verdict=AllAir "
|
||||
"calls=%llu hits=%llu region_hits=%llu stores=%llu"),
|
||||
RegionOrigin.X, RegionOrigin.Y, RegionOrigin.Z,
|
||||
static_cast<unsigned long long>(Snapshot.Counters[CallsIndex]),
|
||||
static_cast<unsigned long long>(Snapshot.Counters[HitsIndex]),
|
||||
static_cast<unsigned long long>(Snapshot.Counters[RegionHitsIndex]),
|
||||
static_cast<unsigned long long>(Snapshot.Counters[StoresIndex]));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UVoxelForgeExploreCommandlet::UVoxelForgeExploreCommandlet()
|
||||
@@ -5189,6 +5526,16 @@ int32 RunExploreCase(const FString& Params, FString* OutJson)
|
||||
}
|
||||
const double SetupSeconds = FPlatformTime::Seconds() - SetupStartSeconds;
|
||||
|
||||
if (Arguments.bProfileTileVerdictCache)
|
||||
{
|
||||
if (!RunTileVerdictCacheProbe(Arguments, World, Error))
|
||||
{
|
||||
UE_LOG(LogTemp, Error,
|
||||
TEXT("[VoxelForgeExplore] tile-verdict cache probe failed: %s"), *Error);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool bRequestedModeFailed = false;
|
||||
if (Arguments.bOpBounds)
|
||||
{
|
||||
@@ -5303,9 +5650,25 @@ int32 RunExploreCase(const FString& Params, FString* OutJson)
|
||||
}
|
||||
else if (Arguments.bRender)
|
||||
{
|
||||
// A render-only invocation still has to obey the inside-walkable-space contract. Run the
|
||||
// same focused walk as a private seed pass; it is intentionally omitted from the render-only
|
||||
// JSON so the selected mode remains render.
|
||||
if (Arguments.bRenderCameraOverride)
|
||||
{
|
||||
// Controlled diagnostic renders can supply a player-fit seed captured from a baseline
|
||||
// run. This keeps the mesh region, camera pose, and raster target identical while a
|
||||
// single generator writer is toggled. The density guard in RunRender still refuses a
|
||||
// stale/inside-rock pose.
|
||||
RenderSeedWalk.CameraSeedPoses.Add(Arguments.RenderCamera);
|
||||
RenderSeedWalk.CameraSeedSources.Add(TEXT("render_camera_override"));
|
||||
RenderSeedWalk.Status = TEXT("override");
|
||||
Output.WalkSeconds = 0.0;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeExplore] render camera seed override (%.3f, %.3f, %.3f)"),
|
||||
Arguments.RenderCamera.X, Arguments.RenderCamera.Y, Arguments.RenderCamera.Z);
|
||||
}
|
||||
else
|
||||
{
|
||||
// A render-only invocation still has to obey the inside-walkable-space contract. Run
|
||||
// the same focused walk as a private seed pass; it is intentionally omitted from the
|
||||
// render-only JSON so the selected mode remains render.
|
||||
const double Start = FPlatformTime::Seconds();
|
||||
const bool bWalkOk = RunWalk(Arguments, World, RenderSeedWalk, Budget);
|
||||
Output.WalkSeconds = FPlatformTime::Seconds() - Start;
|
||||
@@ -5313,8 +5676,10 @@ int32 RunExploreCase(const FString& Params, FString* OutJson)
|
||||
{
|
||||
bRequestedModeFailed = true;
|
||||
}
|
||||
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeExplore] render camera seed walk %.3fs (%s)"),
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeExplore] render camera seed walk %.3fs (%s)"),
|
||||
Output.WalkSeconds, *RenderSeedWalk.Status);
|
||||
}
|
||||
CameraSeedWalk = &RenderSeedWalk;
|
||||
// Keep the private render seed capture available to the run-level shared-grid report;
|
||||
// the walk object is still omitted from the public JSON when walk was not requested.
|
||||
@@ -5944,6 +6309,9 @@ bool BuildBatchCaseParams(
|
||||
{ TEXT("render_step"), TEXT("renderstep") },
|
||||
{ TEXT("render_max_distance"), TEXT("rendermaxdistance") },
|
||||
{ TEXT("surface_roughness"), TEXT("surfaceroughness") },
|
||||
{ TEXT("roughness_frequency"), TEXT("roughnessfrequency") },
|
||||
{ TEXT("room_mouth_rise_strength"), TEXT("roommouthrise") },
|
||||
{ TEXT("room_mouth_rise_blend_voxels"), TEXT("roommouthriseblend") },
|
||||
{ TEXT("tunnel_floor_step_height"), TEXT("tunnelfloorstep") },
|
||||
{ TEXT("tunnel_floor_max_ledge_height"), TEXT("tunnelfloormaxledge") },
|
||||
{ TEXT("tunnel_floor_gentle_slope"), TEXT("tunnelfloorgentle") },
|
||||
@@ -5956,6 +6324,12 @@ bool BuildBatchCaseParams(
|
||||
}
|
||||
}
|
||||
|
||||
FString RoughnessType;
|
||||
if (JsonString(Case, TEXT("roughness_noise_type"), RoughnessType))
|
||||
{
|
||||
OutParams += FString::Printf(TEXT(" -roughnesstype=%s"), *RoughnessType);
|
||||
}
|
||||
|
||||
const TPair<const TCHAR*, const TCHAR*> IntegerFloorFields[] = {
|
||||
{ TEXT("tunnel_floor_ledge_count"), TEXT("tunnelfloorledges") },
|
||||
{ TEXT("tunnel_floor_max_ledges"), TEXT("tunnelfloormaxledges") },
|
||||
|
||||
Reference in new issue
Block a user