test(measure): VerticalShafts breaks the primordial law in 15 of 16 seeds
The margin is exonerated. Every earlier run excluded 8 voxels at each strate boundary, and voxels 4-8 of those are ordinary interior, not seal -- the same window mistake that produced "FlatPlain is 99.95% void" at margin 32. So the query was re-run at margin 0, 2, 4 and 8. All four stay negative; margin 0 includes the seal itself and still reports negative. (The implementation had clamped explicit margins to >= 1, which made the margin-0 check impossible; Codex found and fixed that.) Diagnosis -- this is not a near-miss wall: arrival LowerPoint (65.8, 95.8, -1047.25) departure UpperPoint (88.4, -104.6, -991.58) XY separation 201.7 voxels (~50 m), Z delta 55.7 arrival component 22,531 cells (22.0% of air) departure component 21,621 cells (21.1% of air) nearest approach between the two components: 135-179 voxels Two large, healthy, entirely separate shaft networks half a strate apart. Nothing to breach; the archetype has no lateral connection at all. 16-seed sweep: 1 pass, 15 fail. Not one unlucky world -- structural. So: descend into a VerticalShafts strate and you probably cannot reach its exit. This is the "live hole in the shipping game" COMPOSER-NOTES Tier 1 predicted, now measured instead of assumed. Stated precisely, because it names the fix: VF_SuggestLandingPoint makes each mouth land in open space, but NOTHING makes the two mouths of one strate land in the SAME open space. New: VF_DiagnoseConnectivity returns component sizes and the spatial separation between the two mouths' components, so a near-miss and a never-meant-to-connect read differently instead of both being "false". No generation change, no threshold tuning. The fix is a design decision. Build clean, 19 succeeded / 0 warnings / 0 failed. Sweeps 3.4 s + 6.9 s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bp7FxoQcSoiuhxjuLr6aBM
This commit is contained in:
1 parent
cd31dec869
commit
0bbfdeb7a7
4 files changed
+526
-27
No files matched your search
@@ -246,6 +246,7 @@ namespace
|
||||
{
|
||||
int32 SampleStep = 0;
|
||||
int32 RadiusInVoxels = 0;
|
||||
int32 RequestedMarginVoxels = -1;
|
||||
FVoxelStrateMetrics Metrics;
|
||||
FConnectivityProbe Probe;
|
||||
};
|
||||
@@ -1037,6 +1038,7 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
{
|
||||
int32 SampleStep;
|
||||
int32 RadiusInVoxels;
|
||||
int32 InteriorMarginVoxels = -1;
|
||||
};
|
||||
static constexpr FRefinementCase Cases[] = {
|
||||
{4, 256},
|
||||
@@ -1056,10 +1058,12 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
FRefinementSweepRow& Row = Rows.AddDefaulted_GetRef();
|
||||
Row.SampleStep = TestCase.SampleStep;
|
||||
Row.RadiusInVoxels = TestCase.RadiusInVoxels;
|
||||
Row.RequestedMarginVoxels = TestCase.InteriorMarginVoxels;
|
||||
|
||||
FVoxelStrateMeasureSettings RowSettings = BaseSettings;
|
||||
RowSettings.SampleStep = TestCase.SampleStep;
|
||||
RowSettings.RadiusInVoxels = TestCase.RadiusInVoxels;
|
||||
RowSettings.InteriorMarginVoxels = TestCase.InteriorMarginVoxels;
|
||||
|
||||
Row.Metrics = VF_MeasureStrate(
|
||||
*World.Generator,
|
||||
@@ -1125,6 +1129,106 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
}
|
||||
const double SweepSeconds = FPlatformTime::Seconds() - SweepStartSeconds;
|
||||
|
||||
static constexpr int32 MarginCases[] = {0, 2, 4, 8};
|
||||
TArray<FRefinementSweepRow> MarginRows;
|
||||
MarginRows.Reserve(UE_ARRAY_COUNT(MarginCases));
|
||||
|
||||
const double MarginSweepStartSeconds = FPlatformTime::Seconds();
|
||||
for (const int32 RequestedMargin : MarginCases)
|
||||
{
|
||||
FRefinementSweepRow& Row = MarginRows.AddDefaulted_GetRef();
|
||||
Row.SampleStep = 2;
|
||||
Row.RadiusInVoxels = 192;
|
||||
Row.RequestedMarginVoxels = RequestedMargin;
|
||||
|
||||
FVoxelStrateMeasureSettings RowSettings = BaseSettings;
|
||||
RowSettings.SampleStep = Row.SampleStep;
|
||||
RowSettings.RadiusInVoxels = Row.RadiusInVoxels;
|
||||
RowSettings.InteriorMarginVoxels = RequestedMargin;
|
||||
|
||||
Row.Metrics = VF_MeasureStrate(
|
||||
*World.Generator,
|
||||
*World.StrateManager,
|
||||
VerticalShaftsIndex,
|
||||
RowSettings);
|
||||
|
||||
Row.Probe.bChecked = true;
|
||||
Row.Probe.Result = VF_AreConnected(
|
||||
*World.Generator,
|
||||
*World.StrateManager,
|
||||
VerticalShaftsIndex,
|
||||
ArrivalPoint,
|
||||
DeparturePoint,
|
||||
RowSettings,
|
||||
Row.Probe.bStartSnapped,
|
||||
Row.Probe.bGoalSnapped);
|
||||
|
||||
if (!Row.Metrics.bValid
|
||||
|| Row.Metrics.NumSampled <= 0
|
||||
|| Row.Metrics.NumSampled > BaseSettings.MaxCells
|
||||
|| Row.Metrics.NumAir <= 0
|
||||
|| Row.Metrics.NumSolid <= 0)
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: VerticalShafts margin row margin=%d did not produce a "
|
||||
"bounded non-vacuous measurement (valid=%s, reason='%s', cells=%lld, "
|
||||
"air=%lld, solid=%lld)."),
|
||||
RequestedMargin,
|
||||
Row.Metrics.bValid ? TEXT("true") : TEXT("false"),
|
||||
*Row.Metrics.RefusalReason,
|
||||
Row.Metrics.NumSampled,
|
||||
Row.Metrics.NumAir,
|
||||
Row.Metrics.NumSolid));
|
||||
bAllChecksPassed = false;
|
||||
}
|
||||
|
||||
if (Row.Metrics.ResolvedMarginVoxels != RequestedMargin)
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: VerticalShafts margin row requested margin=%d but the "
|
||||
"measurement resolved margin=%d."),
|
||||
RequestedMargin,
|
||||
Row.Metrics.ResolvedMarginVoxels));
|
||||
bAllChecksPassed = false;
|
||||
}
|
||||
|
||||
if (Row.Probe.Result == EVoxelConnectivityResult::OutOfWindow
|
||||
|| Row.Probe.Result == EVoxelConnectivityResult::StartCellSolid
|
||||
|| Row.Probe.Result == EVoxelConnectivityResult::GoalCellSolid)
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: VerticalShafts margin row margin=%d could not query both "
|
||||
"in-window air mouths: %s."),
|
||||
RequestedMargin,
|
||||
*ConnectivityProbeText(Row.Probe)));
|
||||
bAllChecksPassed = false;
|
||||
}
|
||||
|
||||
if (Row.Probe.bStartSnapped || Row.Probe.bGoalSnapped)
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: VerticalShafts margin row margin=%d snapped an endpoint; "
|
||||
"the controlled comparison is not a mouth-to-mouth query."),
|
||||
RequestedMargin));
|
||||
bAllChecksPassed = false;
|
||||
}
|
||||
}
|
||||
const double MarginSweepSeconds = FPlatformTime::Seconds() - MarginSweepStartSeconds;
|
||||
|
||||
bool bMarginArtifact = false;
|
||||
bool bMarginSweepUsable = MarginRows.Num() == UE_ARRAY_COUNT(MarginCases);
|
||||
for (const FRefinementSweepRow& Row : MarginRows)
|
||||
{
|
||||
bMarginArtifact |= Row.Probe.Result == EVoxelConnectivityResult::Connected;
|
||||
bMarginSweepUsable &= Row.Metrics.bValid
|
||||
&& Row.Metrics.ResolvedMarginVoxels == Row.RequestedMarginVoxels
|
||||
&& Row.Probe.Result != EVoxelConnectivityResult::OutOfWindow
|
||||
&& Row.Probe.Result != EVoxelConnectivityResult::StartCellSolid
|
||||
&& Row.Probe.Result != EVoxelConnectivityResult::GoalCellSolid
|
||||
&& !Row.Probe.bStartSnapped
|
||||
&& !Row.Probe.bGoalSnapped;
|
||||
}
|
||||
|
||||
FString Summary = TEXT(
|
||||
"VerticalShafts arrival->departure refinement (seed 1337; source mouth pair; "
|
||||
"derived interior window):\n");
|
||||
@@ -1158,6 +1262,199 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
||||
Summary += FString::Printf(
|
||||
TEXT("VerticalShafts refinement sweep wall-clock: %.3f seconds.\n"),
|
||||
SweepSeconds);
|
||||
|
||||
Summary += TEXT(
|
||||
"Part A — margin artifact check (SampleStep 2, RadiusInVoxels 192; explicit margins):\n");
|
||||
Summary += TEXT(
|
||||
" margin | verdict | components | largest share | sampled cells | Z window | endpoint snaps\n");
|
||||
for (const FRefinementSweepRow& Row : MarginRows)
|
||||
{
|
||||
const FString VerdictText = ConnectivityProbeText(Row.Probe);
|
||||
FString SnapText = TEXT("none");
|
||||
if (Row.Probe.bStartSnapped || Row.Probe.bGoalSnapped)
|
||||
{
|
||||
SnapText = ConnectivityProbeText(Row.Probe);
|
||||
}
|
||||
Summary += FString::Printf(
|
||||
TEXT(" %d | %s | %d | %.9g | %lld | [%d,%d) | %s\n"),
|
||||
Row.RequestedMarginVoxels,
|
||||
*VerdictText,
|
||||
Row.Metrics.NumAirComponents,
|
||||
Row.Metrics.LargestComponentShare,
|
||||
Row.Metrics.NumSampled,
|
||||
Row.Metrics.SampledMinZ,
|
||||
Row.Metrics.SampledMaxZ,
|
||||
*SnapText);
|
||||
}
|
||||
Summary += FString::Printf(
|
||||
TEXT("Part A verdict: %s. Margin sweep wall-clock: %.3f seconds.\n"),
|
||||
bMarginArtifact
|
||||
? TEXT("ARTIFACT — at least one margin-0/2/4 query is CONNECTED; the default margin excluded the connection")
|
||||
: TEXT("FINDING SURVIVES — no margin-0/2/4 query is CONNECTED; the full window is exonerated"),
|
||||
MarginSweepSeconds);
|
||||
|
||||
if (!bMarginArtifact && bMarginSweepUsable)
|
||||
{
|
||||
FVoxelStrateMeasureSettings DiagnosticSettings = BaseSettings;
|
||||
DiagnosticSettings.SampleStep = 2;
|
||||
DiagnosticSettings.RadiusInVoxels = 192;
|
||||
DiagnosticSettings.InteriorMarginVoxels = 0;
|
||||
|
||||
const FVoxelConnectivityDiagnostics Diagnostics = VF_DiagnoseConnectivity(
|
||||
*World.Generator,
|
||||
*World.StrateManager,
|
||||
VerticalShaftsIndex,
|
||||
ArrivalPoint,
|
||||
DeparturePoint,
|
||||
DiagnosticSettings);
|
||||
if (!Diagnostics.bValid
|
||||
|| Diagnostics.NumAirCells <= 0
|
||||
|| Diagnostics.StartComponentCells <= 0
|
||||
|| Diagnostics.GoalComponentCells <= 0
|
||||
|| Diagnostics.StartToGoalComponentDistanceCells < 0
|
||||
|| Diagnostics.GoalToStartComponentDistanceCells < 0)
|
||||
{
|
||||
AddError(TEXT("HARD FAILURE: the surviving VerticalShafts finding could not produce "
|
||||
"component and separation diagnostics."));
|
||||
bAllChecksPassed = false;
|
||||
}
|
||||
|
||||
const float MouthXYSeparation = FMath::Sqrt(
|
||||
FMath::Square(ArrivalPoint.X - DeparturePoint.X)
|
||||
+ FMath::Square(ArrivalPoint.Y - DeparturePoint.Y));
|
||||
const float MouthZDelta = DeparturePoint.Z - ArrivalPoint.Z;
|
||||
const float MouthStraightLine = FVector::Dist(ArrivalPoint, DeparturePoint);
|
||||
const float CoarseStraightLineCells = MouthStraightLine
|
||||
/ static_cast<float>(DiagnosticSettings.SampleStep);
|
||||
|
||||
Summary += TEXT("Part B — VerticalShafts diagnosis (seed 1337; margin 0; step 2; radius 192):\n");
|
||||
Summary += FString::Printf(
|
||||
TEXT(" arrival LowerPoint: (%.3f, %.3f, %.3f); departure UpperPoint: "
|
||||
"(%.3f, %.3f, %.3f); XY separation %.3f voxels; Z delta %.3f voxels.\n"),
|
||||
ArrivalPoint.X,
|
||||
ArrivalPoint.Y,
|
||||
ArrivalPoint.Z,
|
||||
DeparturePoint.X,
|
||||
DeparturePoint.Y,
|
||||
DeparturePoint.Z,
|
||||
MouthXYSeparation,
|
||||
MouthZDelta);
|
||||
Summary += FString::Printf(
|
||||
TEXT(" mouth components: air=%lld; arrival=%lld (share %.9g); departure=%lld "
|
||||
"(share %.9g).\n"),
|
||||
Diagnostics.NumAirCells,
|
||||
Diagnostics.StartComponentCells,
|
||||
Diagnostics.StartComponentShare,
|
||||
Diagnostics.GoalComponentCells,
|
||||
Diagnostics.GoalComponentShare);
|
||||
Summary += FString::Printf(
|
||||
TEXT(" coarse-grid separation: straight-line %.3f cells (%.3f voxels); "
|
||||
"arrival to nearest departure-component cell %.3f cells (%.3f voxels); "
|
||||
"departure to nearest arrival-component cell %.3f cells (%.3f voxels).\n"),
|
||||
CoarseStraightLineCells,
|
||||
MouthStraightLine,
|
||||
Diagnostics.StartToGoalComponentDistanceCells,
|
||||
Diagnostics.StartToGoalComponentDistanceCells * static_cast<float>(DiagnosticSettings.SampleStep),
|
||||
Diagnostics.GoalToStartComponentDistanceCells,
|
||||
Diagnostics.GoalToStartComponentDistanceCells * static_cast<float>(DiagnosticSettings.SampleStep));
|
||||
Summary += FString::Printf(
|
||||
TEXT(" diagnostic route verdict: %s; endpoint snaps: start=%s, goal=%s.\n"),
|
||||
ConnectivityResultName(Diagnostics.Result),
|
||||
Diagnostics.bStartSnapped ? TEXT("yes") : TEXT("no"),
|
||||
Diagnostics.bGoalSnapped ? TEXT("yes") : TEXT("no"));
|
||||
|
||||
static constexpr int32 SeedCases[] = {
|
||||
1337, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9, 10, 11, 12, 13, 14, 15,
|
||||
};
|
||||
int32 SeedPasses = 0;
|
||||
int32 SeedFailures = 0;
|
||||
int32 SeedSnapEvents = 0;
|
||||
int32 SeedResultCounts[6] = {};
|
||||
const double SeedSweepStartSeconds = FPlatformTime::Seconds();
|
||||
for (const int32 Seed : SeedCases)
|
||||
{
|
||||
FTestWorld SeedWorld;
|
||||
SeedWorld.Build(Seed, /*InGapChunks=*/2);
|
||||
if (!SeedWorld.IsValid())
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: seed %d could not build the VerticalShafts fixture."),
|
||||
Seed));
|
||||
++SeedFailures;
|
||||
continue;
|
||||
}
|
||||
|
||||
FVector SeedArrivalPoint = FVector::ZeroVector;
|
||||
FVector SeedDeparturePoint = FVector::ZeroVector;
|
||||
if (!FindChainMouths(
|
||||
SeedWorld.StrateManager->GetPassages(),
|
||||
VerticalShaftsIndex,
|
||||
SeedArrivalPoint,
|
||||
SeedDeparturePoint))
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: seed %d did not produce exactly one VerticalShafts "
|
||||
"arrival and departure mouth."),
|
||||
Seed));
|
||||
++SeedFailures;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool bStartSnapped = false;
|
||||
bool bGoalSnapped = false;
|
||||
const EVoxelConnectivityResult SeedResult = VF_AreConnected(
|
||||
*SeedWorld.Generator,
|
||||
*SeedWorld.StrateManager,
|
||||
VerticalShaftsIndex,
|
||||
SeedArrivalPoint,
|
||||
SeedDeparturePoint,
|
||||
DiagnosticSettings,
|
||||
bStartSnapped,
|
||||
bGoalSnapped);
|
||||
++SeedResultCounts[static_cast<int32>(SeedResult)];
|
||||
const int32 SnapEvents = (bStartSnapped ? 1 : 0) + (bGoalSnapped ? 1 : 0);
|
||||
SeedSnapEvents += SnapEvents;
|
||||
if (SeedResult == EVoxelConnectivityResult::Connected
|
||||
&& !bStartSnapped
|
||||
&& !bGoalSnapped)
|
||||
{
|
||||
++SeedPasses;
|
||||
}
|
||||
else
|
||||
{
|
||||
++SeedFailures;
|
||||
}
|
||||
}
|
||||
const double SeedSweepSeconds = FPlatformTime::Seconds() - SeedSweepStartSeconds;
|
||||
|
||||
Summary += FString::Printf(
|
||||
TEXT(" multi-seed arrival->departure sweep (%d seeds; margin 0, step 2, radius 192): "
|
||||
"%d pass, %d fail, %d snapped endpoint events; wall-clock %.3f seconds.\n"),
|
||||
UE_ARRAY_COUNT(SeedCases),
|
||||
SeedPasses,
|
||||
SeedFailures,
|
||||
SeedSnapEvents,
|
||||
SeedSweepSeconds);
|
||||
Summary += FString::Printf(
|
||||
TEXT(" seed result breakdown: CONNECTED=%d, NOT_CONNECTED_AT_THIS_RESOLUTION=%d, "
|
||||
"START_CELL_SOLID=%d, GOAL_CELL_SOLID=%d, OUT_OF_WINDOW=%d, COARSE_LIED=%d.\n"),
|
||||
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::Connected)],
|
||||
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::NotConnectedAtThisResolution)],
|
||||
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::StartCellSolid)],
|
||||
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::GoalCellSolid)],
|
||||
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::OutOfWindow)],
|
||||
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::CoarseLied)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Summary += FString::Printf(
|
||||
TEXT("Part B diagnosis and multi-seed sweep: SKIPPED because Part A %s.\n"),
|
||||
bMarginArtifact
|
||||
? TEXT("found the connection in the previously excluded band")
|
||||
: TEXT("did not produce a usable four-row measurement/query set"));
|
||||
}
|
||||
|
||||
AddInfo(Summary);
|
||||
|
||||
return bAllChecksPassed;
|
||||
|
||||
@@ -164,14 +164,14 @@ namespace VoxelStrateMeasurePrivate
|
||||
}
|
||||
else if (DoubleSealThickness <= static_cast<float>(INT32_MIN))
|
||||
{
|
||||
RequestedMargin = 1;
|
||||
RequestedMargin = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
RequestedMargin = FMath::CeilToInt(DoubleSealThickness);
|
||||
}
|
||||
}
|
||||
const int32 ResolvedMargin = FMath::Clamp(RequestedMargin, 1, MaxMargin);
|
||||
const int32 ResolvedMargin = FMath::Clamp(RequestedMargin, 0, MaxMargin);
|
||||
|
||||
const int64 InteriorMinZ = StrateBottomZ + static_cast<int64>(ResolvedMargin);
|
||||
const int64 InteriorMaxZ = StrateTopZ - static_cast<int64>(ResolvedMargin);
|
||||
@@ -803,6 +803,94 @@ namespace VoxelStrateMeasurePrivate
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
EVoxelConnectivityResult EvaluateConnectivityOnGrid(
|
||||
const UVoxelGenerator& Generator,
|
||||
const FSampleGrid& Grid,
|
||||
const FVector& AVoxel,
|
||||
const FVector& BVoxel,
|
||||
int32& OutStart,
|
||||
int32& OutGoal,
|
||||
bool& bOutStartSnapped,
|
||||
bool& bOutGoalSnapped)
|
||||
{
|
||||
OutStart = -1;
|
||||
OutGoal = -1;
|
||||
bOutStartSnapped = false;
|
||||
bOutGoalSnapped = false;
|
||||
|
||||
const EEndpointCellResult StartCell = ResolveEndpointCell(
|
||||
Grid, AVoxel, OutStart, bOutStartSnapped);
|
||||
const EEndpointCellResult GoalCell = ResolveEndpointCell(
|
||||
Grid, BVoxel, OutGoal, bOutGoalSnapped);
|
||||
if (StartCell == EEndpointCellResult::OutOfWindow
|
||||
|| GoalCell == EEndpointCellResult::OutOfWindow)
|
||||
{
|
||||
return EVoxelConnectivityResult::OutOfWindow;
|
||||
}
|
||||
if (StartCell == EEndpointCellResult::Solid)
|
||||
{
|
||||
return EVoxelConnectivityResult::StartCellSolid;
|
||||
}
|
||||
if (GoalCell == EEndpointCellResult::Solid)
|
||||
{
|
||||
return EVoxelConnectivityResult::GoalCellSolid;
|
||||
}
|
||||
|
||||
TArray<int32> Path;
|
||||
if (!FindCoarsePath(Grid, OutStart, OutGoal, Path))
|
||||
{
|
||||
return EVoxelConnectivityResult::NotConnectedAtThisResolution;
|
||||
}
|
||||
|
||||
// The coarse route is evidence, not a pass. Re-check this one BFS route at one-voxel
|
||||
// spacing in the original voxel field. A single solid sample is the dangerous coarse
|
||||
// false positive.
|
||||
if (!FullResolutionPathIsAir(Generator, Grid, AVoxel, BVoxel, Path))
|
||||
{
|
||||
return EVoxelConnectivityResult::CoarseLied;
|
||||
}
|
||||
return EVoxelConnectivityResult::Connected;
|
||||
}
|
||||
|
||||
float DistanceToComponent(
|
||||
const FSampleGrid& Grid,
|
||||
int32 StartCell,
|
||||
int32 TargetComponent,
|
||||
const TArray<int32>& Components)
|
||||
{
|
||||
if (Components[StartCell] == TargetComponent)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
int32 StartX = 0;
|
||||
int32 StartY = 0;
|
||||
int32 StartZ = 0;
|
||||
DecodeIndex(Grid, StartCell, StartX, StartY, StartZ);
|
||||
int64 BestDistanceSquared = MAX_int64;
|
||||
for (int32 Index = 0; Index < Grid.CellCount; ++Index)
|
||||
{
|
||||
if (Components[Index] != TargetComponent)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int32 X = 0;
|
||||
int32 Y = 0;
|
||||
int32 Z = 0;
|
||||
DecodeIndex(Grid, Index, X, Y, Z);
|
||||
const int64 DX = static_cast<int64>(X) - static_cast<int64>(StartX);
|
||||
const int64 DY = static_cast<int64>(Y) - static_cast<int64>(StartY);
|
||||
const int64 DZ = static_cast<int64>(Z) - static_cast<int64>(StartZ);
|
||||
const int64 DistanceSquared = DX * DX + DY * DY + DZ * DZ;
|
||||
BestDistanceSquared = FMath::Min(BestDistanceSquared, DistanceSquared);
|
||||
}
|
||||
|
||||
return BestDistanceSquared == MAX_int64
|
||||
? -1.0f
|
||||
: FMath::Sqrt(static_cast<float>(BestDistanceSquared));
|
||||
}
|
||||
}
|
||||
|
||||
FVoxelStrateMetrics VF_MeasureStrate(
|
||||
@@ -859,38 +947,109 @@ EVoxelConnectivityResult VF_AreConnected(
|
||||
|
||||
int32 Start = -1;
|
||||
int32 Goal = -1;
|
||||
const VoxelStrateMeasurePrivate::EEndpointCellResult StartCell =
|
||||
VoxelStrateMeasurePrivate::ResolveEndpointCell(
|
||||
Grid, AVoxel, Start, bOutStartSnapped);
|
||||
const VoxelStrateMeasurePrivate::EEndpointCellResult GoalCell =
|
||||
VoxelStrateMeasurePrivate::ResolveEndpointCell(
|
||||
Grid, BVoxel, Goal, bOutGoalSnapped);
|
||||
if (StartCell == VoxelStrateMeasurePrivate::EEndpointCellResult::OutOfWindow
|
||||
|| GoalCell == VoxelStrateMeasurePrivate::EEndpointCellResult::OutOfWindow)
|
||||
return VoxelStrateMeasurePrivate::EvaluateConnectivityOnGrid(
|
||||
Generator,
|
||||
Grid,
|
||||
AVoxel,
|
||||
BVoxel,
|
||||
Start,
|
||||
Goal,
|
||||
bOutStartSnapped,
|
||||
bOutGoalSnapped);
|
||||
}
|
||||
|
||||
FVoxelConnectivityDiagnostics VF_DiagnoseConnectivity(
|
||||
const UVoxelGenerator& Generator,
|
||||
const UVoxelStrateManager& Manager,
|
||||
int32 StrateIndex,
|
||||
const FVector& AVoxel,
|
||||
const FVector& BVoxel,
|
||||
const FVoxelStrateMeasureSettings& Settings)
|
||||
{
|
||||
FVoxelConnectivityDiagnostics Result;
|
||||
if (!FMath::IsFinite(AVoxel.X) || !FMath::IsFinite(AVoxel.Y) || !FMath::IsFinite(AVoxel.Z)
|
||||
|| !FMath::IsFinite(BVoxel.X) || !FMath::IsFinite(BVoxel.Y) || !FMath::IsFinite(BVoxel.Z))
|
||||
{
|
||||
return EVoxelConnectivityResult::OutOfWindow;
|
||||
return Result;
|
||||
}
|
||||
if (StartCell == VoxelStrateMeasurePrivate::EEndpointCellResult::Solid)
|
||||
|
||||
VoxelStrateMeasurePrivate::FSampleGrid Grid;
|
||||
FString RefusalReason;
|
||||
if (!VoxelStrateMeasurePrivate::BuildSampleGrid(
|
||||
Generator, Manager, StrateIndex, Settings, Grid, RefusalReason))
|
||||
{
|
||||
return Result;
|
||||
}
|
||||
|
||||
int32 Start = -1;
|
||||
int32 Goal = -1;
|
||||
Result.Result = VoxelStrateMeasurePrivate::EvaluateConnectivityOnGrid(
|
||||
Generator,
|
||||
Grid,
|
||||
AVoxel,
|
||||
BVoxel,
|
||||
Start,
|
||||
Goal,
|
||||
Result.bStartSnapped,
|
||||
Result.bGoalSnapped);
|
||||
Result.bValid = Result.Result != EVoxelConnectivityResult::OutOfWindow
|
||||
|| (Start >= 0 && Goal >= 0);
|
||||
if (Start < 0 || Goal < 0)
|
||||
{
|
||||
return EVoxelConnectivityResult::StartCellSolid;
|
||||
return Result;
|
||||
}
|
||||
if (GoalCell == VoxelStrateMeasurePrivate::EEndpointCellResult::Solid)
|
||||
|
||||
TArray<int32> Components;
|
||||
int32 NumComponents = 0;
|
||||
int64 LargestComponentCells = 0;
|
||||
int32 LargestComponentLowestCell = INDEX_NONE;
|
||||
int32 NumComponentsAtLeast1Pct = 0;
|
||||
int64 NumAir = 0;
|
||||
for (const uint8 bAir : Grid.Air)
|
||||
{
|
||||
NumAir += bAir != 0u ? 1 : 0;
|
||||
}
|
||||
FloodFillAir(
|
||||
Grid,
|
||||
Components,
|
||||
NumComponents,
|
||||
NumAir,
|
||||
LargestComponentCells,
|
||||
LargestComponentLowestCell,
|
||||
NumComponentsAtLeast1Pct);
|
||||
|
||||
const int32 StartComponent = Components[Start];
|
||||
const int32 GoalComponent = Components[Goal];
|
||||
if (StartComponent < 0 || GoalComponent < 0)
|
||||
{
|
||||
return EVoxelConnectivityResult::GoalCellSolid;
|
||||
return Result;
|
||||
}
|
||||
|
||||
TArray<int32> Path;
|
||||
if (!VoxelStrateMeasurePrivate::FindCoarsePath(Grid, Start, Goal, Path))
|
||||
TArray<int32> ComponentCells;
|
||||
ComponentCells.Init(0, NumComponents);
|
||||
for (const int32 Component : Components)
|
||||
{
|
||||
return EVoxelConnectivityResult::NotConnectedAtThisResolution;
|
||||
if (Component >= 0)
|
||||
{
|
||||
++ComponentCells[Component];
|
||||
}
|
||||
}
|
||||
|
||||
// The coarse route is evidence, not a pass. Re-check this one BFS route at one-voxel spacing
|
||||
// in the original voxel field. A single solid sample is the dangerous coarse false positive.
|
||||
if (!VoxelStrateMeasurePrivate::FullResolutionPathIsAir(
|
||||
Generator, Grid, AVoxel, BVoxel, Path))
|
||||
Result.NumAirCells = NumAir;
|
||||
Result.StartComponentCells = ComponentCells[StartComponent];
|
||||
Result.GoalComponentCells = ComponentCells[GoalComponent];
|
||||
if (NumAir > 0)
|
||||
{
|
||||
return EVoxelConnectivityResult::CoarseLied;
|
||||
Result.StartComponentShare = static_cast<float>(
|
||||
static_cast<double>(Result.StartComponentCells) / static_cast<double>(NumAir));
|
||||
Result.GoalComponentShare = static_cast<float>(
|
||||
static_cast<double>(Result.GoalComponentCells) / static_cast<double>(NumAir));
|
||||
}
|
||||
return EVoxelConnectivityResult::Connected;
|
||||
Result.StartToGoalComponentDistanceCells =
|
||||
VoxelStrateMeasurePrivate::DistanceToComponent(
|
||||
Grid, Start, GoalComponent, Components);
|
||||
Result.GoalToStartComponentDistanceCells =
|
||||
VoxelStrateMeasurePrivate::DistanceToComponent(
|
||||
Grid, Goal, StartComponent, Components);
|
||||
return Result;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ struct VOXELFORGE_API FVoxelStrateMeasureSettings
|
||||
FVector2D CenterXY = FVector2D::ZeroVector; // Actor-space voxel coordinates.
|
||||
int32 MaxCells = 8000000; // Refuse a grid larger than this many cells.
|
||||
int32 HeadroomCells = 2; // Air cells above a floor cell required for walkable.
|
||||
int32 InteriorMarginVoxels = -1; // <0 = derive from BoundarySealThickness (2x, clamped).
|
||||
int32 InteriorMarginVoxels = -1; // <0 = derive from BoundarySealThickness (2x, clamped); explicit 0 includes the seal.
|
||||
};
|
||||
|
||||
/** Metrics derived from one coarse grid and its single air flood fill. */
|
||||
@@ -55,6 +55,33 @@ struct VOXELFORGE_API FVoxelStrateMetrics
|
||||
int32 SampledMaxZ = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component facts for one coarse mouth-to-mouth query.
|
||||
*
|
||||
* The component shares are fractions of the sampled air cells. The two component distances are
|
||||
* exact spatial Euclidean distances in coarse-cell coordinates to the nearest cell of the other
|
||||
* mouth's component. Solid cells do not participate in that proximity calculation because it
|
||||
* measures geometric separation between air components, not an alternate route. A distance of
|
||||
* zero means both mouths resolve to the same component. Negative distances mean that the
|
||||
* requested grid or endpoint could not be analyzed.
|
||||
*/
|
||||
struct VOXELFORGE_API FVoxelConnectivityDiagnostics
|
||||
{
|
||||
bool bValid = false;
|
||||
EVoxelConnectivityResult Result = EVoxelConnectivityResult::OutOfWindow;
|
||||
bool bStartSnapped = false;
|
||||
bool bGoalSnapped = false;
|
||||
|
||||
int64 NumAirCells = 0;
|
||||
int64 StartComponentCells = 0;
|
||||
int64 GoalComponentCells = 0;
|
||||
float StartComponentShare = 0.0f;
|
||||
float GoalComponentShare = 0.0f;
|
||||
|
||||
float StartToGoalComponentDistanceCells = -1.0f;
|
||||
float GoalToStartComponentDistanceCells = -1.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* Measure one strate in actor-space voxel coordinates.
|
||||
*
|
||||
@@ -87,3 +114,17 @@ VOXELFORGE_API EVoxelConnectivityResult VF_AreConnected(
|
||||
const FVoxelStrateMeasureSettings& Settings,
|
||||
bool& bOutStartSnapped,
|
||||
bool& bOutGoalSnapped);
|
||||
|
||||
/**
|
||||
* Return the component sizes and spatial separation facts for the same query performed by
|
||||
* VF_AreConnected. The returned Result uses the same full-resolution route re-check. bValid means
|
||||
* the sample grid was built and both endpoint coordinates were in its bounds; component fields
|
||||
* remain zero/negative when an endpoint has no air cell to analyze.
|
||||
*/
|
||||
VOXELFORGE_API FVoxelConnectivityDiagnostics VF_DiagnoseConnectivity(
|
||||
const UVoxelGenerator& Generator,
|
||||
const UVoxelStrateManager& Manager,
|
||||
int32 StrateIndex,
|
||||
const FVector& AVoxel,
|
||||
const FVector& BVoxel,
|
||||
const FVoxelStrateMeasureSettings& Settings);
|
||||
Reference in new issue
Block a user