fix(measure): ask the primordial law the right question
Three defects, two of them mine. 1. The anchor was a guess. LargestComponentAnchor() returned a hardcoded point near the (0,0) spine and the comment admitted it, so NOT_CONNECTED meant "not connected to a guessed point". The flood fill already knows the answer; it now returns LargestComponentPoint (tie- broken by lowest cell index, so it stays deterministic), LargestComponentCells and NumComponentsAtLeast1Pct. 2. "Connected to the largest component" is NOT the primordial law. The law is that a player must not descend into a place they cannot leave -- a question about MOUTHS. A strate can be 57 disconnected shafts and be fine, provided the shaft you arrive in is the shaft you leave from. The test now asks arrival -> departure per strate: the LowerPoint of the passage arriving, to the UpperPoint of the passage departing. 3. A false verdict and a true one were indistinguishable. VF_AreConnected returned false both when no route existed and when the endpoint's own coarse cell sampled solid -- and a passage mouth is a carved tube whose cell centre lands in adjacent rock easily. It now returns EVoxelConnectivityResult and snaps to an adjacent air cell first, reporting the repair. RESULT: the earlier "5 of 15 endpoints disconnected" was an ARTIFACT of the guessed anchor plus the wrong question. Corrected: 13 of 15 connected, 0 StartCellSolid, 0 GoalCellSolid. One real finding survives. arrival -> departure passes for 5 of 6 applicable strates; VerticalShafts FAILS -- its arrival mouth is in the largest component, its departure mouth is not. NOT yet reported as a bug: a coarse grid can falsely SEPARATE as well as falsely join, and a connecting corridor thinner than SampleStep vanishes. Next task settles it at a finer step. Also noted: GetPassages() contains a self-referential surface entry (0 -> 0), excluded from the chain law. Build clean, 18 succeeded / 0 warnings / 0 failed, verified in VoxelM.log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bp7FxoQcSoiuhxjuLr6aBM
This commit is contained in:
1 parent
13399529b5
commit
efcc8fcb65
4 files changed
+634
-148
No files matched your search
+8
-5
@@ -307,15 +307,18 @@ Headless, read-only Tier 2 measurement pass. `VF_MeasureStrate` samples one stra
|
||||
boundary-seal-derived (or explicitly overridden) interior margin into a bounded coarse grid,
|
||||
performs one deterministic 6-connected air flood fill, and derives fractions, components,
|
||||
walkability, feature scale, and clearance from that grid. Metrics report the resolved margin and
|
||||
the inclusive/exclusive voxel Z window used.
|
||||
the inclusive/exclusive voxel Z window used, plus the largest component's deterministic lowest-cell
|
||||
representative point, cell count, and count of components holding at least 1% of the air.
|
||||
`VF_AreConnected` recovers a deterministic BFS parent path from the same kind of grid and checks
|
||||
only that route at full voxel resolution before returning true; a coarse false positive sets
|
||||
`bOutCoarseLied`. No UObject state, cache, actor, world, or PIE is required.
|
||||
only that route at full voxel resolution before returning `Connected`. It returns an explicit
|
||||
`EVoxelConnectivityResult`; a solid endpoint cell is repaired by snapping to the nearest air cell
|
||||
among its 26 neighbours when possible, and reports the snap through its out flags. No UObject state,
|
||||
cache, actor, world, or PIE is required.
|
||||
| Symbol | Role |
|
||||
|--------|------|
|
||||
| `FVoxelStrateMeasureSettings` / `FVoxelStrateMetrics` | Plain settings/result structs for bounded strate sampling and derived measurements; callers may override the interior margin, and results identify the resolved margin/Z window. |
|
||||
| `FVoxelStrateMeasureSettings` / `FVoxelStrateMetrics` | Plain settings/result structs for bounded strate sampling and derived measurements; callers may override the interior margin, and results identify the resolved margin/Z window and deterministic component representatives. |
|
||||
| `VF_MeasureStrate` | One-grid/one-flood-fill strate metrics; refuses invalid bounds or a grid over `MaxCells`. |
|
||||
| `VF_AreConnected` | Coarse 6-connected BFS plus one full-resolution recheck of its recovered route. |
|
||||
| `VF_AreConnected` | Coarse 6-connected BFS plus one full-resolution recheck of its recovered route, with explicit endpoint-solid, out-of-window, coarse-lie, and connectivity outcomes. |
|
||||
|
||||
### 3.7 Cave morphology (SDF rooms/tunnels) — `Public/VoxelCaveMorphology.h` + `.cpp`
|
||||
Header is rich with inline docs. Two namespaces + a per-chunk cache system.
|
||||
|
||||
@@ -25,16 +25,27 @@ namespace
|
||||
FVoxelStrateMetrics DerivedWindowRepeat;
|
||||
};
|
||||
|
||||
struct FPassageReport
|
||||
struct FConnectivityProbe
|
||||
{
|
||||
bool bChecked = false;
|
||||
EVoxelConnectivityResult Result = EVoxelConnectivityResult::OutOfWindow;
|
||||
bool bStartSnapped = false;
|
||||
bool bGoalSnapped = false;
|
||||
};
|
||||
|
||||
struct FStrateConnectivityReport
|
||||
{
|
||||
int32 Index = INDEX_NONE;
|
||||
FString Name;
|
||||
int32 UpperStrateIndex = INDEX_NONE;
|
||||
int32 LowerStrateIndex = INDEX_NONE;
|
||||
FVector UpperPoint = FVector::ZeroVector;
|
||||
FVector LowerPoint = FVector::ZeroVector;
|
||||
FString UpperResult;
|
||||
FString LowerResult;
|
||||
int32 ArrivalPassageIndex = INDEX_NONE;
|
||||
int32 DeparturePassageIndex = INDEX_NONE;
|
||||
FVector ArrivalPoint = FVector::ZeroVector;
|
||||
FVector DeparturePoint = FVector::ZeroVector;
|
||||
bool bHasArrival = false;
|
||||
bool bHasDeparture = false;
|
||||
FConnectivityProbe ArrivalToDeparture;
|
||||
FConnectivityProbe ArrivalToLargest;
|
||||
FConnectivityProbe DepartureToLargest;
|
||||
};
|
||||
|
||||
FString ArchetypeName(const FStrateSlot& Slot)
|
||||
@@ -55,6 +66,13 @@ namespace
|
||||
return FMemory::Memcmp(&A, &B, sizeof(float)) == 0;
|
||||
}
|
||||
|
||||
bool SameVectorBits(const FVector& A, const FVector& B)
|
||||
{
|
||||
return SameFloatBits(A.X, B.X)
|
||||
&& SameFloatBits(A.Y, B.Y)
|
||||
&& SameFloatBits(A.Z, B.Z);
|
||||
}
|
||||
|
||||
bool MetricsAreBitIdentical(const FVoxelStrateMetrics& A, const FVoxelStrateMetrics& B)
|
||||
{
|
||||
return A.bValid == B.bValid
|
||||
@@ -65,6 +83,9 @@ namespace
|
||||
&& SameFloatBits(A.AirFraction, B.AirFraction)
|
||||
&& A.NumAirComponents == B.NumAirComponents
|
||||
&& SameFloatBits(A.LargestComponentShare, B.LargestComponentShare)
|
||||
&& SameVectorBits(A.LargestComponentPoint, B.LargestComponentPoint)
|
||||
&& A.LargestComponentCells == B.LargestComponentCells
|
||||
&& A.NumComponentsAtLeast1Pct == B.NumComponentsAtLeast1Pct
|
||||
&& SameFloatBits(A.WalkableFraction, B.WalkableFraction)
|
||||
&& SameFloatBits(A.MedianFeatureScale, B.MedianFeatureScale)
|
||||
&& A.MedianVerticalClearance == B.MedianVerticalClearance
|
||||
@@ -73,34 +94,45 @@ namespace
|
||||
&& A.SampledMaxZ == B.SampledMaxZ;
|
||||
}
|
||||
|
||||
bool IsInsideMeasuredWindow(
|
||||
const FVoxelStrateMetrics& Metrics,
|
||||
const FVoxelStrateMeasureSettings& Settings,
|
||||
const FVector& Point)
|
||||
const TCHAR* ConnectivityResultName(EVoxelConnectivityResult Result)
|
||||
{
|
||||
const float MinX = Settings.CenterXY.X - static_cast<float>(Settings.RadiusInVoxels);
|
||||
const float MaxX = Settings.CenterXY.X + static_cast<float>(Settings.RadiusInVoxels);
|
||||
const float MinY = Settings.CenterXY.Y - static_cast<float>(Settings.RadiusInVoxels);
|
||||
const float MaxY = Settings.CenterXY.Y + static_cast<float>(Settings.RadiusInVoxels);
|
||||
return Point.X >= MinX && Point.X < MaxX
|
||||
&& Point.Y >= MinY && Point.Y < MaxY
|
||||
&& Point.Z >= static_cast<float>(Metrics.SampledMinZ)
|
||||
&& Point.Z < static_cast<float>(Metrics.SampledMaxZ);
|
||||
switch (Result)
|
||||
{
|
||||
case EVoxelConnectivityResult::Connected: return TEXT("CONNECTED");
|
||||
case EVoxelConnectivityResult::NotConnected: return TEXT("NOT_CONNECTED");
|
||||
case EVoxelConnectivityResult::StartCellSolid: return TEXT("START_CELL_SOLID");
|
||||
case EVoxelConnectivityResult::GoalCellSolid: return TEXT("GOAL_CELL_SOLID");
|
||||
case EVoxelConnectivityResult::OutOfWindow: return TEXT("OUT_OF_WINDOW");
|
||||
case EVoxelConnectivityResult::CoarseLied: return TEXT("COARSE_LIED");
|
||||
default: return TEXT("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
FVector LargestComponentAnchor(
|
||||
const FStrateSlot& Slot,
|
||||
const FVector2D& CenterXY,
|
||||
int32 SampleStep)
|
||||
FString ConnectivityProbeText(const FConnectivityProbe& Probe)
|
||||
{
|
||||
// The fixture's origin spine is the deterministic hub/void anchor. The measurement API
|
||||
// intentionally returns metrics rather than a component coordinate, so this report probes
|
||||
// the known spine point that represents the largest reachable component in this fixture.
|
||||
// Keep the established anchor one full chunk above the strate floor. The derived window
|
||||
// includes it, while the first derived sample cell may still be floor rock for slab fields.
|
||||
const float AnchorZ = (static_cast<float>(Slot.BottomChunkZ) + 1.0f) * CHUNK_SIZE
|
||||
+ 0.5f * static_cast<float>(SampleStep);
|
||||
return FVector(CenterXY.X + 2.0f, CenterXY.Y + 2.0f, AnchorZ);
|
||||
if (!Probe.bChecked)
|
||||
{
|
||||
return TEXT("NOT_CHECKED");
|
||||
}
|
||||
|
||||
FString Text = ConnectivityResultName(Probe.Result);
|
||||
if (Probe.bStartSnapped || Probe.bGoalSnapped)
|
||||
{
|
||||
Text += TEXT(" [");
|
||||
bool bNeedSeparator = false;
|
||||
if (Probe.bStartSnapped)
|
||||
{
|
||||
Text += TEXT("start snapped");
|
||||
bNeedSeparator = true;
|
||||
}
|
||||
if (Probe.bGoalSnapped)
|
||||
{
|
||||
if (bNeedSeparator) Text += TEXT(", ");
|
||||
Text += TEXT("goal snapped");
|
||||
}
|
||||
Text += TEXT("]");
|
||||
}
|
||||
return Text;
|
||||
}
|
||||
|
||||
bool FullResolutionDirectSegmentHasSolid(
|
||||
@@ -239,8 +271,8 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
||||
* 2. Vacuity: a main measurement with no samples, no air, or no solid measures nothing.
|
||||
* 3. Range sanity: fractions/components must describe an actual air field.
|
||||
* 4. Determinism: identical inputs must produce bit-identical metrics.
|
||||
* 5. Coarse-lie guard: a coarse route must never be reported connected after a full-resolution
|
||||
* solid sample; the controlled 3-voxel wall below must make bOutCoarseLied fire.
|
||||
* 5. Coarse-lie guard: a coarse route must never be reported CONNECTED after a
|
||||
* full-resolution solid sample; the controlled 3-voxel wall below must return COARSE_LIED.
|
||||
*/
|
||||
|
||||
int32 GapTopChunkZ = 0;
|
||||
@@ -349,6 +381,15 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
||||
|| !FMath::IsFinite(Metrics.WalkableFraction)
|
||||
|| Metrics.WalkableFraction < 0.0f || Metrics.WalkableFraction > 1.0f
|
||||
|| (Metrics.NumAir > 0 && Metrics.NumAirComponents == 0)
|
||||
|| Metrics.LargestComponentCells < 0
|
||||
|| Metrics.LargestComponentCells > Metrics.NumAir
|
||||
|| Metrics.NumComponentsAtLeast1Pct < 0
|
||||
|| Metrics.NumComponentsAtLeast1Pct > Metrics.NumAirComponents
|
||||
|| (Metrics.NumAir > 0 && Metrics.LargestComponentCells == 0)
|
||||
|| (Metrics.NumAir > 0
|
||||
&& (!FMath::IsFinite(Metrics.LargestComponentPoint.X)
|
||||
|| !FMath::IsFinite(Metrics.LargestComponentPoint.Y)
|
||||
|| !FMath::IsFinite(Metrics.LargestComponentPoint.Z)))
|
||||
|| (Metrics.LargestComponentShare == 0.0f && Metrics.AirFraction > 0.05f)
|
||||
|| Metrics.ResolvedMarginVoxels < 1
|
||||
|| Metrics.SampledMinZ >= Metrics.SampledMaxZ)
|
||||
@@ -408,20 +449,22 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
||||
GapAirSamples,
|
||||
GapSamples);
|
||||
Summary += TEXT("Archetypes (old one-chunk vs derived window; derived InteriorMarginVoxels<0 => 2x BoundarySealThickness, clamped):\n");
|
||||
Summary += TEXT(" name | old margin | old Z span [min,max) | old Air | old Largest | old Walkable | old FeatureScale vox | old Clearance vox | old Components | old Samples | old Solid | derived margin | derived Z span [min,max) | derived Air | derived Largest | derived Walkable | derived FeatureScale vox | derived Clearance vox | derived Components | derived Samples | derived Solid\n");
|
||||
Summary += TEXT(" name | old margin | old Z span [min,max) | old Air | old Largest | old LargestCells | old >=1% | old Walkable | old FeatureScale vox | old Clearance vox | old Components | old Samples | old Solid | derived margin | derived Z span [min,max) | derived Air | derived Largest | derived LargestCells | derived >=1% | derived Walkable | derived FeatureScale vox | derived Clearance vox | derived Components | derived Samples | derived Solid\n");
|
||||
for (const FArchetypeReport& Report : ArchetypeReports)
|
||||
{
|
||||
const FVoxelStrateMetrics& Old = Report.OldWindow;
|
||||
const FVoxelStrateMetrics& Derived = Report.DerivedWindow;
|
||||
Summary += FString::Printf(
|
||||
TEXT(" %s | %d | [%d,%d) | %.9g | %.9g | %.9g | %.9g | %d | %d | %lld | %lld | "
|
||||
"%d | [%d,%d) | %.9g | %.9g | %.9g | %.9g | %d | %d | %lld | %lld\n"),
|
||||
TEXT(" %s | %d | [%d,%d) | %.9g | %.9g | %lld | %d | %.9g | %.9g | %d | %d | %lld | %lld | "
|
||||
"%d | [%d,%d) | %.9g | %.9g | %lld | %d | %.9g | %.9g | %d | %d | %lld | %lld\n"),
|
||||
*Report.Name,
|
||||
Old.ResolvedMarginVoxels,
|
||||
Old.SampledMinZ,
|
||||
Old.SampledMaxZ,
|
||||
Old.AirFraction,
|
||||
Old.LargestComponentShare,
|
||||
Old.LargestComponentCells,
|
||||
Old.NumComponentsAtLeast1Pct,
|
||||
Old.WalkableFraction,
|
||||
Old.MedianFeatureScale,
|
||||
Old.MedianVerticalClearance,
|
||||
@@ -433,6 +476,8 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
||||
Derived.SampledMaxZ,
|
||||
Derived.AirFraction,
|
||||
Derived.LargestComponentShare,
|
||||
Derived.LargestComponentCells,
|
||||
Derived.NumComponentsAtLeast1Pct,
|
||||
Derived.WalkableFraction,
|
||||
Derived.MedianFeatureScale,
|
||||
Derived.MedianVerticalClearance,
|
||||
@@ -443,6 +488,81 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
||||
|
||||
int32 NumRoutesChecked = 0;
|
||||
int32 NumRoutesRefuted = 0;
|
||||
int32 NumGuardRoutesChecked = 0;
|
||||
int32 NumGuardRoutesRefuted = 0;
|
||||
int32 NumSnappedEndpointEvents = 0;
|
||||
int32 NumArrivalDepartureRoutesChecked = 0;
|
||||
int32 NumArrivalDepartureSnappedEndpointEvents = 0;
|
||||
|
||||
int32 NumAnchorProbes = 0;
|
||||
int32 NumAnchorConnected = 0;
|
||||
int32 NumAnchorNotConnected = 0;
|
||||
int32 NumAnchorStartCellSolid = 0;
|
||||
int32 NumAnchorGoalCellSolid = 0;
|
||||
int32 NumAnchorOutOfWindow = 0;
|
||||
int32 NumAnchorCoarseLied = 0;
|
||||
int32 NumAnchorSnappedEndpointEvents = 0;
|
||||
|
||||
auto ProbeRoute = [&](int32 StrateIndex,
|
||||
const FVector& Start,
|
||||
const FVector& Goal,
|
||||
bool bCountInGuard,
|
||||
bool bCountAsAnchorProbe,
|
||||
bool bCountAsArrivalDeparture) -> FConnectivityProbe
|
||||
{
|
||||
FConnectivityProbe Probe;
|
||||
Probe.bChecked = true;
|
||||
Probe.Result = VF_AreConnected(
|
||||
*World.Generator,
|
||||
*World.StrateManager,
|
||||
StrateIndex,
|
||||
Start,
|
||||
Goal,
|
||||
DerivedWindowSettings,
|
||||
Probe.bStartSnapped,
|
||||
Probe.bGoalSnapped);
|
||||
|
||||
++NumRoutesChecked;
|
||||
if (Probe.Result == EVoxelConnectivityResult::CoarseLied)
|
||||
{
|
||||
++NumRoutesRefuted;
|
||||
}
|
||||
if (bCountInGuard)
|
||||
{
|
||||
++NumGuardRoutesChecked;
|
||||
if (Probe.Result == EVoxelConnectivityResult::CoarseLied)
|
||||
{
|
||||
++NumGuardRoutesRefuted;
|
||||
}
|
||||
}
|
||||
|
||||
const int32 SnappedHere = (Probe.bStartSnapped ? 1 : 0)
|
||||
+ (Probe.bGoalSnapped ? 1 : 0);
|
||||
NumSnappedEndpointEvents += SnappedHere;
|
||||
if (bCountAsArrivalDeparture)
|
||||
{
|
||||
++NumArrivalDepartureRoutesChecked;
|
||||
NumArrivalDepartureSnappedEndpointEvents += SnappedHere;
|
||||
}
|
||||
|
||||
if (bCountAsAnchorProbe)
|
||||
{
|
||||
++NumAnchorProbes;
|
||||
NumAnchorSnappedEndpointEvents += SnappedHere;
|
||||
switch (Probe.Result)
|
||||
{
|
||||
case EVoxelConnectivityResult::Connected: ++NumAnchorConnected; break;
|
||||
case EVoxelConnectivityResult::NotConnected: ++NumAnchorNotConnected; break;
|
||||
case EVoxelConnectivityResult::StartCellSolid: ++NumAnchorStartCellSolid; break;
|
||||
case EVoxelConnectivityResult::GoalCellSolid: ++NumAnchorGoalCellSolid; break;
|
||||
case EVoxelConnectivityResult::OutOfWindow: ++NumAnchorOutOfWindow; break;
|
||||
case EVoxelConnectivityResult::CoarseLied: ++NumAnchorCoarseLied; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
return Probe;
|
||||
};
|
||||
|
||||
for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex)
|
||||
{
|
||||
const FStrateSlot& Slot = Layout[StrateIndex];
|
||||
@@ -455,22 +575,15 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
||||
const FVector B(Settings.CenterXY.X + 2.0f, Settings.CenterXY.Y + 2.0f,
|
||||
static_cast<float>(Window.SampledMaxZ)
|
||||
- 0.5f * static_cast<float>(DerivedWindowSettings.SampleStep));
|
||||
bool bCoarseLied = false;
|
||||
const bool bConnected = VF_AreConnected(
|
||||
*World.Generator,
|
||||
*World.StrateManager,
|
||||
StrateIndex,
|
||||
A,
|
||||
B,
|
||||
DerivedWindowSettings,
|
||||
bCoarseLied);
|
||||
++NumRoutesChecked;
|
||||
if (bCoarseLied) ++NumRoutesRefuted;
|
||||
if (bConnected && FullResolutionDirectSegmentHasSolid(*World.Generator, A, B))
|
||||
const FConnectivityProbe Probe = ProbeRoute(
|
||||
StrateIndex, A, B, /*bCountInGuard=*/true, /*bCountAsAnchorProbe=*/false,
|
||||
/*bCountAsArrivalDeparture=*/false);
|
||||
if (Probe.Result == EVoxelConnectivityResult::Connected
|
||||
&& FullResolutionDirectSegmentHasSolid(*World.Generator, A, B))
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: VF_AreConnected returned true for %s while its full-resolution "
|
||||
"direct walk contained a solid sample."),
|
||||
TEXT("HARD FAILURE: VF_AreConnected returned CONNECTED for %s while its "
|
||||
"full-resolution direct walk contained a solid sample."),
|
||||
*ArchetypeName(Slot)));
|
||||
bAllChecksPassed = false;
|
||||
}
|
||||
@@ -498,102 +611,315 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
||||
bAllChecksPassed = false;
|
||||
}
|
||||
|
||||
bool bWallCoarseLied = false;
|
||||
const bool bWallConnected = VF_AreConnected(
|
||||
*World.Generator,
|
||||
*World.StrateManager,
|
||||
0,
|
||||
WallA,
|
||||
WallB,
|
||||
DerivedWindowSettings,
|
||||
bWallCoarseLied);
|
||||
++NumRoutesChecked;
|
||||
if (bWallCoarseLied) ++NumRoutesRefuted;
|
||||
if (bWallConnected)
|
||||
const FConnectivityProbe WallProbe = ProbeRoute(
|
||||
0, WallA, WallB, /*bCountInGuard=*/true, /*bCountAsAnchorProbe=*/false,
|
||||
/*bCountAsArrivalDeparture=*/false);
|
||||
if (WallProbe.Result != EVoxelConnectivityResult::CoarseLied)
|
||||
{
|
||||
AddError(TEXT("HARD FAILURE: VF_AreConnected reported true across a full-resolution solid 3-voxel wall."));
|
||||
bAllChecksPassed = false;
|
||||
}
|
||||
if (!bWallCoarseLied)
|
||||
{
|
||||
AddError(TEXT("HARD FAILURE: the controlled coarse-connectivity lie did not set bOutCoarseLied."));
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: the controlled coarse-connectivity lie returned %s instead of "
|
||||
"COARSE_LIED."),
|
||||
ConnectivityResultName(WallProbe.Result)));
|
||||
bAllChecksPassed = false;
|
||||
}
|
||||
World.DiffLayer->Clear();
|
||||
|
||||
TArray<FPassageReport> PassageReports;
|
||||
const TArray<FVoxelPassage>& Passages = World.StrateManager->GetPassages();
|
||||
PassageReports.Reserve(Passages.Num());
|
||||
TArray<int32> ArrivalPassageByStrate;
|
||||
TArray<int32> DeparturePassageByStrate;
|
||||
ArrivalPassageByStrate.Init(INDEX_NONE, Layout.Num());
|
||||
DeparturePassageByStrate.Init(INDEX_NONE, Layout.Num());
|
||||
int32 SurfaceEntryPassageIndex = INDEX_NONE;
|
||||
|
||||
for (int32 PassageIndex = 0; PassageIndex < Passages.Num(); ++PassageIndex)
|
||||
{
|
||||
const FVoxelPassage& Passage = Passages[PassageIndex];
|
||||
FPassageReport& Report = PassageReports.AddDefaulted_GetRef();
|
||||
Report.Index = PassageIndex;
|
||||
Report.Name = FString::Printf(TEXT("%03d"), PassageIndex);
|
||||
Report.UpperStrateIndex = Passage.UpperStrateIndex;
|
||||
Report.LowerStrateIndex = Passage.LowerStrateIndex;
|
||||
Report.UpperPoint = Passage.UpperPoint;
|
||||
Report.LowerPoint = Passage.LowerPoint;
|
||||
|
||||
auto CheckEndpoint = [&](int32 EndpointStrateIndex, const FVector& Endpoint) -> FString
|
||||
if (Passage.UpperStrateIndex == Passage.LowerStrateIndex)
|
||||
{
|
||||
if (!Layout.IsValidIndex(EndpointStrateIndex)) return TEXT("invalid-strate");
|
||||
const FVoxelStrateMetrics& EndpointMetrics =
|
||||
DerivedMetricsByIndex[EndpointStrateIndex];
|
||||
if (!EndpointMetrics.bValid) return TEXT("invalid-window");
|
||||
if (!IsInsideMeasuredWindow(EndpointMetrics, DerivedWindowSettings, Endpoint))
|
||||
if (SurfaceEntryPassageIndex == INDEX_NONE)
|
||||
{
|
||||
return TEXT("out-of-window");
|
||||
SurfaceEntryPassageIndex = PassageIndex;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const FStrateSlot& Slot = Layout[EndpointStrateIndex];
|
||||
const FVector Anchor = LargestComponentAnchor(
|
||||
Slot,
|
||||
DerivedWindowSettings.CenterXY,
|
||||
DerivedWindowSettings.SampleStep);
|
||||
bool bCoarseLied = false;
|
||||
const bool bConnected = VF_AreConnected(
|
||||
*World.Generator,
|
||||
*World.StrateManager,
|
||||
EndpointStrateIndex,
|
||||
Endpoint,
|
||||
Anchor,
|
||||
DerivedWindowSettings,
|
||||
bCoarseLied);
|
||||
++NumRoutesChecked;
|
||||
if (bCoarseLied) ++NumRoutesRefuted;
|
||||
return bConnected ? TEXT("CONNECTED_TO_LARGEST") : TEXT("NOT_CONNECTED");
|
||||
};
|
||||
if (Passage.LowerStrateIndex != Passage.UpperStrateIndex + 1)
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: passage %d is not between consecutive strates (%d -> %d)."),
|
||||
PassageIndex,
|
||||
Passage.UpperStrateIndex,
|
||||
Passage.LowerStrateIndex));
|
||||
bAllChecksPassed = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
Report.UpperResult = CheckEndpoint(Passage.UpperStrateIndex, Passage.UpperPoint);
|
||||
Report.LowerResult = CheckEndpoint(Passage.LowerStrateIndex, Passage.LowerPoint);
|
||||
if (!Layout.IsValidIndex(Passage.UpperStrateIndex)
|
||||
|| !Layout.IsValidIndex(Passage.LowerStrateIndex))
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: passage %d references a strate outside the layout (%d -> %d)."),
|
||||
PassageIndex,
|
||||
Passage.UpperStrateIndex,
|
||||
Passage.LowerStrateIndex));
|
||||
bAllChecksPassed = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ArrivalPassageByStrate[Passage.LowerStrateIndex] != INDEX_NONE)
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: strate %d has more than one arrival passage."),
|
||||
Passage.LowerStrateIndex));
|
||||
bAllChecksPassed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ArrivalPassageByStrate[Passage.LowerStrateIndex] = PassageIndex;
|
||||
}
|
||||
|
||||
if (DeparturePassageByStrate[Passage.UpperStrateIndex] != INDEX_NONE)
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: strate %d has more than one departure passage."),
|
||||
Passage.UpperStrateIndex));
|
||||
bAllChecksPassed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
DeparturePassageByStrate[Passage.UpperStrateIndex] = PassageIndex;
|
||||
}
|
||||
}
|
||||
|
||||
PassageReports.Sort([](const FPassageReport& A, const FPassageReport& B)
|
||||
for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex)
|
||||
{
|
||||
return A.Index < B.Index;
|
||||
});
|
||||
if (StrateIndex > 0 && ArrivalPassageByStrate[StrateIndex] == INDEX_NONE)
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: non-topmost strate %d has no arrival passage."),
|
||||
StrateIndex));
|
||||
bAllChecksPassed = false;
|
||||
}
|
||||
if (StrateIndex + 1 < Layout.Num()
|
||||
&& DeparturePassageByStrate[StrateIndex] == INDEX_NONE)
|
||||
{
|
||||
AddError(FString::Printf(
|
||||
TEXT("HARD FAILURE: non-bottom-most strate %d has no departure passage."),
|
||||
StrateIndex));
|
||||
bAllChecksPassed = false;
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FStrateConnectivityReport> StrateReports;
|
||||
StrateReports.SetNum(Layout.Num());
|
||||
for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex)
|
||||
{
|
||||
FStrateConnectivityReport& Report = StrateReports[StrateIndex];
|
||||
Report.Index = StrateIndex;
|
||||
Report.Name = ArchetypeName(Layout[StrateIndex]);
|
||||
|
||||
if (ArrivalPassageByStrate[StrateIndex] != INDEX_NONE)
|
||||
{
|
||||
Report.bHasArrival = true;
|
||||
Report.ArrivalPassageIndex = ArrivalPassageByStrate[StrateIndex];
|
||||
Report.ArrivalPoint = Passages[Report.ArrivalPassageIndex].LowerPoint;
|
||||
}
|
||||
if (DeparturePassageByStrate[StrateIndex] != INDEX_NONE)
|
||||
{
|
||||
Report.bHasDeparture = true;
|
||||
Report.DeparturePassageIndex = DeparturePassageByStrate[StrateIndex];
|
||||
Report.DeparturePoint = Passages[Report.DeparturePassageIndex].UpperPoint;
|
||||
}
|
||||
}
|
||||
|
||||
// The actual largest-component comparison uses the representative point returned by the
|
||||
// measurement API. The surface-entry lower mouth remains an auxiliary guard probe so this
|
||||
// task continues to re-report the established 15 endpoint comparison probes; it is not the
|
||||
// topmost strate's chain arrival.
|
||||
for (FStrateConnectivityReport& Report : StrateReports)
|
||||
{
|
||||
const FVoxelStrateMetrics& Metrics = DerivedMetricsByIndex[Report.Index];
|
||||
if (!Metrics.bValid) continue;
|
||||
|
||||
if (Report.bHasArrival)
|
||||
{
|
||||
Report.ArrivalToLargest = ProbeRoute(
|
||||
Report.Index,
|
||||
Report.ArrivalPoint,
|
||||
Metrics.LargestComponentPoint,
|
||||
/*bCountInGuard=*/true,
|
||||
/*bCountAsAnchorProbe=*/true,
|
||||
/*bCountAsArrivalDeparture=*/false);
|
||||
}
|
||||
if (Report.bHasDeparture)
|
||||
{
|
||||
Report.DepartureToLargest = ProbeRoute(
|
||||
Report.Index,
|
||||
Report.DeparturePoint,
|
||||
Metrics.LargestComponentPoint,
|
||||
/*bCountInGuard=*/true,
|
||||
/*bCountAsAnchorProbe=*/true,
|
||||
/*bCountAsArrivalDeparture=*/false);
|
||||
}
|
||||
}
|
||||
|
||||
if (SurfaceEntryPassageIndex != INDEX_NONE && Layout.IsValidIndex(0)
|
||||
&& DerivedMetricsByIndex[0].bValid)
|
||||
{
|
||||
const FVoxelPassage& SurfaceEntry = Passages[SurfaceEntryPassageIndex];
|
||||
ProbeRoute(
|
||||
0,
|
||||
SurfaceEntry.LowerPoint,
|
||||
DerivedMetricsByIndex[0].LargestComponentPoint,
|
||||
/*bCountInGuard=*/true,
|
||||
/*bCountAsAnchorProbe=*/true,
|
||||
/*bCountAsArrivalDeparture=*/false);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddError(TEXT("HARD FAILURE: the surface-entry auxiliary endpoint probe is missing."));
|
||||
bAllChecksPassed = false;
|
||||
}
|
||||
|
||||
int32 NumArrivalDeparturePasses = 0;
|
||||
TArray<FString> FailedStrates;
|
||||
for (FStrateConnectivityReport& Report : StrateReports)
|
||||
{
|
||||
if (!Report.bHasArrival || !Report.bHasDeparture)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Report.ArrivalToDeparture = ProbeRoute(
|
||||
Report.Index,
|
||||
Report.ArrivalPoint,
|
||||
Report.DeparturePoint,
|
||||
/*bCountInGuard=*/false,
|
||||
/*bCountAsAnchorProbe=*/false,
|
||||
/*bCountAsArrivalDeparture=*/true);
|
||||
if (Report.ArrivalToDeparture.Result == EVoxelConnectivityResult::Connected)
|
||||
{
|
||||
++NumArrivalDeparturePasses;
|
||||
}
|
||||
else
|
||||
{
|
||||
const FString ResultText = ConnectivityProbeText(Report.ArrivalToDeparture);
|
||||
FailedStrates.Add(FString::Printf(
|
||||
TEXT("%d:%s (%s)"),
|
||||
Report.Index,
|
||||
*Report.Name,
|
||||
*ResultText));
|
||||
}
|
||||
}
|
||||
|
||||
FString FailedStrateText = TEXT("none");
|
||||
if (FailedStrates.Num() > 0)
|
||||
{
|
||||
FailedStrateText = FString::Join(FailedStrates, TEXT(", "));
|
||||
}
|
||||
|
||||
Summary += FString::Printf(
|
||||
TEXT("Connectivity guard (derived window): %d routes checked, %d coarse routes refuted at full resolution.\n"),
|
||||
TEXT("Coarse-lie guard (8 interior routes + wall control + 15 endpoint->largest probes): "
|
||||
"%d routes checked, %d coarse routes refuted at full resolution.\n"),
|
||||
NumGuardRoutesChecked,
|
||||
NumGuardRoutesRefuted);
|
||||
Summary += FString::Printf(
|
||||
TEXT("All connectivity probes (including arrival->departure law queries): %d routes "
|
||||
"checked, %d coarse routes refuted at full resolution.\n"),
|
||||
NumRoutesChecked,
|
||||
NumRoutesRefuted);
|
||||
Summary += TEXT("Passages (derived window; each endpoint -> its strate's largest-component spine anchor, sorted):\n");
|
||||
for (const FPassageReport& Report : PassageReports)
|
||||
Summary += FString::Printf(
|
||||
TEXT("Endpoint snap repair: %d snapped endpoint events across all probes; %d in "
|
||||
"%d arrival->departure probes.\n"),
|
||||
NumSnappedEndpointEvents,
|
||||
NumArrivalDepartureSnappedEndpointEvents,
|
||||
NumArrivalDepartureRoutesChecked);
|
||||
Summary += FString::Printf(
|
||||
TEXT("Correct largest-component endpoint probes (%d, including the surface-entry auxiliary): "
|
||||
"connected=%d, not-connected=%d, start-cell-solid=%d, goal-cell-solid=%d, "
|
||||
"out-of-window=%d, coarse-lied=%d, snapped endpoint events=%d.\n"),
|
||||
NumAnchorProbes,
|
||||
NumAnchorConnected,
|
||||
NumAnchorNotConnected,
|
||||
NumAnchorStartCellSolid,
|
||||
NumAnchorGoalCellSolid,
|
||||
NumAnchorOutOfWindow,
|
||||
NumAnchorCoarseLied,
|
||||
NumAnchorSnappedEndpointEvents);
|
||||
Summary += TEXT("Previous \"5 of 15 disconnected\" verdict: ARTIFACT as a claim about sealed "
|
||||
"pockets; it used a guessed anchor and asked the wrong (largest-component) law.\n");
|
||||
Summary += FString::Printf(
|
||||
TEXT("arrival->departure: %d of %d applicable strates pass; failures: %s.\n"),
|
||||
NumArrivalDeparturePasses,
|
||||
NumArrivalDepartureRoutesChecked,
|
||||
*FailedStrateText);
|
||||
|
||||
Summary += TEXT("Strates (chain order; derived window; largest point is the deterministic "
|
||||
"lowest-cell representative):\n");
|
||||
Summary += TEXT(" Strate | arrival->departure | arrival->largest | departure->largest | components | largest share | largest cells | >=1% | largest point\n");
|
||||
for (const FStrateConnectivityReport& Report : StrateReports)
|
||||
{
|
||||
const FVoxelStrateMetrics& Metrics = DerivedMetricsByIndex[Report.Index];
|
||||
FString ArrivalToDeparture;
|
||||
if (Report.bHasArrival && Report.bHasDeparture)
|
||||
{
|
||||
ArrivalToDeparture = ConnectivityProbeText(Report.ArrivalToDeparture);
|
||||
}
|
||||
else if (!Report.bHasArrival && Report.Index == 0)
|
||||
{
|
||||
ArrivalToDeparture = TEXT("N/A (topmost; no arrival)");
|
||||
}
|
||||
else if (!Report.bHasDeparture && Report.Index + 1 == StrateReports.Num())
|
||||
{
|
||||
ArrivalToDeparture = TEXT("N/A (bottom-most; no departure)");
|
||||
}
|
||||
else
|
||||
{
|
||||
ArrivalToDeparture = TEXT("MISSING_CHAIN_MOUTH");
|
||||
}
|
||||
|
||||
FString ArrivalToLargest;
|
||||
if (Report.bHasArrival)
|
||||
{
|
||||
ArrivalToLargest = ConnectivityProbeText(Report.ArrivalToLargest);
|
||||
}
|
||||
else if (Report.Index == 0)
|
||||
{
|
||||
ArrivalToLargest = TEXT("N/A (topmost; no arrival)");
|
||||
}
|
||||
else
|
||||
{
|
||||
ArrivalToLargest = TEXT("MISSING_ARRIVAL");
|
||||
}
|
||||
|
||||
FString DepartureToLargest;
|
||||
if (Report.bHasDeparture)
|
||||
{
|
||||
DepartureToLargest = ConnectivityProbeText(Report.DepartureToLargest);
|
||||
}
|
||||
else if (Report.Index + 1 == StrateReports.Num())
|
||||
{
|
||||
DepartureToLargest = TEXT("N/A (bottom-most; no departure)");
|
||||
}
|
||||
else
|
||||
{
|
||||
DepartureToLargest = TEXT("MISSING_DEPARTURE");
|
||||
}
|
||||
|
||||
Summary += FString::Printf(
|
||||
TEXT(" passage %s: UpperPoint strate=%d (%.3f,%.3f,%.3f) => %s; "
|
||||
"LowerPoint strate=%d (%.3f,%.3f,%.3f) => %s\n"),
|
||||
TEXT(" %d %s | %s | %s | %s | %d | %.9g | %lld | %d | (%.3f,%.3f,%.3f)\n"),
|
||||
Report.Index,
|
||||
*Report.Name,
|
||||
Report.UpperStrateIndex,
|
||||
Report.UpperPoint.X,
|
||||
Report.UpperPoint.Y,
|
||||
Report.UpperPoint.Z,
|
||||
*Report.UpperResult,
|
||||
Report.LowerStrateIndex,
|
||||
Report.LowerPoint.X,
|
||||
Report.LowerPoint.Y,
|
||||
Report.LowerPoint.Z,
|
||||
*Report.LowerResult);
|
||||
*ArrivalToDeparture,
|
||||
*ArrivalToLargest,
|
||||
*DepartureToLargest,
|
||||
Metrics.NumAirComponents,
|
||||
Metrics.LargestComponentShare,
|
||||
Metrics.LargestComponentCells,
|
||||
Metrics.NumComponentsAtLeast1Pct,
|
||||
Metrics.LargestComponentPoint.X,
|
||||
Metrics.LargestComponentPoint.Y,
|
||||
Metrics.LargestComponentPoint.Z);
|
||||
}
|
||||
AddInfo(Summary);
|
||||
|
||||
|
||||
@@ -298,7 +298,10 @@ namespace VoxelStrateMeasurePrivate
|
||||
const FSampleGrid& Grid,
|
||||
TArray<int32>& OutComponents,
|
||||
int32& OutNumComponents,
|
||||
int32& OutLargestComponentCells)
|
||||
int64 NumAir,
|
||||
int64& OutLargestComponentCells,
|
||||
int32& OutLargestComponentLowestCell,
|
||||
int32& OutNumComponentsAtLeast1Pct)
|
||||
{
|
||||
OutComponents.SetNumUninitialized(Grid.CellCount);
|
||||
for (int32 Index = 0; Index < Grid.CellCount; ++Index)
|
||||
@@ -310,6 +313,8 @@ namespace VoxelStrateMeasurePrivate
|
||||
Queue.SetNumUninitialized(Grid.CellCount);
|
||||
OutNumComponents = 0;
|
||||
OutLargestComponentCells = 0;
|
||||
OutLargestComponentLowestCell = INDEX_NONE;
|
||||
OutNumComponentsAtLeast1Pct = 0;
|
||||
|
||||
for (int32 Start = 0; Start < Grid.CellCount; ++Start)
|
||||
{
|
||||
@@ -353,7 +358,20 @@ namespace VoxelStrateMeasurePrivate
|
||||
if (Z > 0) Visit(Grid.Index(X, Y, Z - 1));
|
||||
}
|
||||
|
||||
OutLargestComponentCells = FMath::Max(OutLargestComponentCells, ComponentCells);
|
||||
const int64 ComponentCells64 = static_cast<int64>(ComponentCells);
|
||||
if (NumAir > 0 && ComponentCells64 * 100 >= NumAir)
|
||||
{
|
||||
++OutNumComponentsAtLeast1Pct;
|
||||
}
|
||||
|
||||
if (ComponentCells64 > OutLargestComponentCells
|
||||
|| (ComponentCells64 == OutLargestComponentCells
|
||||
&& (OutLargestComponentLowestCell == INDEX_NONE
|
||||
|| Start < OutLargestComponentLowestCell)))
|
||||
{
|
||||
OutLargestComponentCells = ComponentCells64;
|
||||
OutLargestComponentLowestCell = Start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,8 +447,6 @@ namespace VoxelStrateMeasurePrivate
|
||||
{
|
||||
TArray<int32> Components;
|
||||
int32 NumComponents = 0;
|
||||
int32 LargestComponentCells = 0;
|
||||
FloodFillAir(Grid, Components, NumComponents, LargestComponentCells);
|
||||
|
||||
int64 NumAir = 0;
|
||||
for (const uint8 bAir : Grid.Air)
|
||||
@@ -438,16 +454,38 @@ namespace VoxelStrateMeasurePrivate
|
||||
NumAir += bAir != 0u ? 1 : 0;
|
||||
}
|
||||
|
||||
int64 LargestComponentCells = 0;
|
||||
int32 LargestComponentLowestCell = INDEX_NONE;
|
||||
int32 NumComponentsAtLeast1Pct = 0;
|
||||
FloodFillAir(
|
||||
Grid,
|
||||
Components,
|
||||
NumComponents,
|
||||
NumAir,
|
||||
LargestComponentCells,
|
||||
LargestComponentLowestCell,
|
||||
NumComponentsAtLeast1Pct);
|
||||
|
||||
InOutMetrics.NumSampled = Grid.CellCount;
|
||||
InOutMetrics.NumAir = NumAir;
|
||||
InOutMetrics.NumSolid = static_cast<int64>(Grid.CellCount) - NumAir;
|
||||
InOutMetrics.AirFraction = static_cast<float>(
|
||||
static_cast<double>(NumAir) / static_cast<double>(Grid.CellCount));
|
||||
InOutMetrics.NumAirComponents = NumComponents;
|
||||
InOutMetrics.LargestComponentCells = LargestComponentCells;
|
||||
InOutMetrics.NumComponentsAtLeast1Pct = NumComponentsAtLeast1Pct;
|
||||
InOutMetrics.LargestComponentShare = NumAir > 0
|
||||
? static_cast<float>(static_cast<double>(LargestComponentCells)
|
||||
/ static_cast<double>(NumAir))
|
||||
: 0.0f;
|
||||
if (NumAir > 0 && LargestComponentLowestCell != INDEX_NONE)
|
||||
{
|
||||
int32 LargestX = 0;
|
||||
int32 LargestY = 0;
|
||||
int32 LargestZ = 0;
|
||||
DecodeIndex(Grid, LargestComponentLowestCell, LargestX, LargestY, LargestZ);
|
||||
InOutMetrics.LargestComponentPoint = Grid.CellCenter(LargestX, LargestY, LargestZ);
|
||||
}
|
||||
|
||||
TArray<int32> ClearanceValues;
|
||||
ClearanceValues.Reserve(static_cast<int32>(NumAir));
|
||||
@@ -537,6 +575,93 @@ namespace VoxelStrateMeasurePrivate
|
||||
return true;
|
||||
}
|
||||
|
||||
enum class EEndpointCellResult : uint8
|
||||
{
|
||||
OutOfWindow,
|
||||
Air,
|
||||
Solid
|
||||
};
|
||||
|
||||
EEndpointCellResult ResolveEndpointCell(
|
||||
const FSampleGrid& Grid,
|
||||
const FVector& Point,
|
||||
int32& OutCell,
|
||||
bool& bOutSnapped)
|
||||
{
|
||||
bOutSnapped = false;
|
||||
int32 OriginalCell = -1;
|
||||
if (!FindCellForPoint(Grid, Point, OriginalCell))
|
||||
{
|
||||
return EEndpointCellResult::OutOfWindow;
|
||||
}
|
||||
if (Grid.Air[OriginalCell] != 0u)
|
||||
{
|
||||
OutCell = OriginalCell;
|
||||
return EEndpointCellResult::Air;
|
||||
}
|
||||
|
||||
int32 OriginalX = 0;
|
||||
int32 OriginalY = 0;
|
||||
int32 OriginalZ = 0;
|
||||
DecodeIndex(Grid, OriginalCell, OriginalX, OriginalY, OriginalZ);
|
||||
|
||||
int32 BestCell = INDEX_NONE;
|
||||
float BestDistanceSquared = FLT_MAX;
|
||||
const int32 MaxX = Grid.NumX - 1;
|
||||
const int32 MaxY = Grid.NumY - 1;
|
||||
const int32 MaxZ = Grid.NumZ - 1;
|
||||
const int32 MinX = OriginalX > 0 ? OriginalX - 1 : 0;
|
||||
const int32 MinY = OriginalY > 0 ? OriginalY - 1 : 0;
|
||||
const int32 MinZ = OriginalZ > 0 ? OriginalZ - 1 : 0;
|
||||
const int32 CandidateMaxX = OriginalX < MaxX ? OriginalX + 1 : MaxX;
|
||||
const int32 CandidateMaxY = OriginalY < MaxY ? OriginalY + 1 : MaxY;
|
||||
const int32 CandidateMaxZ = OriginalZ < MaxZ ? OriginalZ + 1 : MaxZ;
|
||||
for (int32 Z = MinZ;
|
||||
Z <= CandidateMaxZ;
|
||||
++Z)
|
||||
{
|
||||
for (int32 Y = MinY;
|
||||
Y <= CandidateMaxY;
|
||||
++Y)
|
||||
{
|
||||
for (int32 X = MinX;
|
||||
X <= CandidateMaxX;
|
||||
++X)
|
||||
{
|
||||
if (X == OriginalX && Y == OriginalY && Z == OriginalZ)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const int32 Candidate = Grid.Index(X, Y, Z);
|
||||
if (Grid.Air[Candidate] == 0u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const float DistanceSquared = FVector::DistSquared(
|
||||
Point, Grid.CellCenter(X, Y, Z));
|
||||
if (DistanceSquared < BestDistanceSquared
|
||||
|| (DistanceSquared == BestDistanceSquared
|
||||
&& (BestCell == INDEX_NONE || Candidate < BestCell)))
|
||||
{
|
||||
BestDistanceSquared = DistanceSquared;
|
||||
BestCell = Candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (BestCell == INDEX_NONE)
|
||||
{
|
||||
return EEndpointCellResult::Solid;
|
||||
}
|
||||
|
||||
OutCell = BestCell;
|
||||
bOutSnapped = true;
|
||||
return EEndpointCellResult::Air;
|
||||
}
|
||||
|
||||
bool FullResolutionAirOnSegment(
|
||||
const UVoxelGenerator& Generator,
|
||||
const FVector& Start,
|
||||
@@ -706,20 +831,22 @@ FVoxelStrateMetrics VF_MeasureStrate(
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool VF_AreConnected(
|
||||
EVoxelConnectivityResult VF_AreConnected(
|
||||
const UVoxelGenerator& Generator,
|
||||
const UVoxelStrateManager& Manager,
|
||||
int32 StrateIndex,
|
||||
const FVector& AVoxel,
|
||||
const FVector& BVoxel,
|
||||
const FVoxelStrateMeasureSettings& Settings,
|
||||
bool& bOutCoarseLied)
|
||||
bool& bOutStartSnapped,
|
||||
bool& bOutGoalSnapped)
|
||||
{
|
||||
bOutCoarseLied = false;
|
||||
bOutStartSnapped = false;
|
||||
bOutGoalSnapped = false;
|
||||
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 false;
|
||||
return EVoxelConnectivityResult::OutOfWindow;
|
||||
}
|
||||
|
||||
VoxelStrateMeasurePrivate::FSampleGrid Grid;
|
||||
@@ -727,21 +854,35 @@ bool VF_AreConnected(
|
||||
if (!VoxelStrateMeasurePrivate::BuildSampleGrid(
|
||||
Generator, Manager, StrateIndex, Settings, Grid, RefusalReason))
|
||||
{
|
||||
return false;
|
||||
return EVoxelConnectivityResult::OutOfWindow;
|
||||
}
|
||||
|
||||
int32 Start = -1;
|
||||
int32 Goal = -1;
|
||||
if (!VoxelStrateMeasurePrivate::FindCellForPoint(Grid, AVoxel, Start)
|
||||
|| !VoxelStrateMeasurePrivate::FindCellForPoint(Grid, BVoxel, Goal))
|
||||
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 false;
|
||||
return EVoxelConnectivityResult::OutOfWindow;
|
||||
}
|
||||
if (StartCell == VoxelStrateMeasurePrivate::EEndpointCellResult::Solid)
|
||||
{
|
||||
return EVoxelConnectivityResult::StartCellSolid;
|
||||
}
|
||||
if (GoalCell == VoxelStrateMeasurePrivate::EEndpointCellResult::Solid)
|
||||
{
|
||||
return EVoxelConnectivityResult::GoalCellSolid;
|
||||
}
|
||||
|
||||
TArray<int32> Path;
|
||||
if (!VoxelStrateMeasurePrivate::FindCoarsePath(Grid, Start, Goal, Path))
|
||||
{
|
||||
return false;
|
||||
return EVoxelConnectivityResult::NotConnected;
|
||||
}
|
||||
|
||||
// The coarse route is evidence, not a pass. Re-check this one BFS route at one-voxel spacing
|
||||
@@ -749,8 +890,7 @@ bool VF_AreConnected(
|
||||
if (!VoxelStrateMeasurePrivate::FullResolutionPathIsAir(
|
||||
Generator, Grid, AVoxel, BVoxel, Path))
|
||||
{
|
||||
bOutCoarseLied = true;
|
||||
return false;
|
||||
return EVoxelConnectivityResult::CoarseLied;
|
||||
}
|
||||
return true;
|
||||
return EVoxelConnectivityResult::Connected;
|
||||
}
|
||||
@@ -7,6 +7,17 @@
|
||||
class UVoxelGenerator;
|
||||
class UVoxelStrateManager;
|
||||
|
||||
/** Explicit outcome of a coarse connectivity query plus its full-resolution route re-check. */
|
||||
enum class EVoxelConnectivityResult : uint8
|
||||
{
|
||||
Connected,
|
||||
NotConnected,
|
||||
StartCellSolid,
|
||||
GoalCellSolid,
|
||||
OutOfWindow,
|
||||
CoarseLied
|
||||
};
|
||||
|
||||
/** Settings for the deterministic coarse grid used by the strate measurement pass. */
|
||||
struct VOXELFORGE_API FVoxelStrateMeasureSettings
|
||||
{
|
||||
@@ -31,6 +42,9 @@ struct VOXELFORGE_API FVoxelStrateMetrics
|
||||
float AirFraction = 0.f;
|
||||
int32 NumAirComponents = 0;
|
||||
float LargestComponentShare = 0.f;
|
||||
FVector LargestComponentPoint = FVector::ZeroVector; // Actor-space voxel coords; valid iff NumAir > 0.
|
||||
int64 LargestComponentCells = 0;
|
||||
int32 NumComponentsAtLeast1Pct = 0;
|
||||
float WalkableFraction = 0.f;
|
||||
float MedianFeatureScale = 0.f;
|
||||
int32 MedianVerticalClearance = 0;
|
||||
@@ -57,14 +71,17 @@ VOXELFORGE_API FVoxelStrateMetrics VF_MeasureStrate(
|
||||
/**
|
||||
* Test coarse connectivity and then re-check the one recovered coarse route at full resolution.
|
||||
*
|
||||
* bOutCoarseLied is true only when the coarse route existed but a full-resolution sample on that
|
||||
* route was solid (or non-finite), in which case this function always returns false.
|
||||
* If an endpoint's own coarse cell is solid, the query searches the 26 neighbouring cells and
|
||||
* uses the nearest air cell when one exists. The snap flags report those repairs. Solid-cell
|
||||
* results are returned only when no adjacent air cell exists. A coarse route that fails its
|
||||
* full-resolution re-check returns CoarseLied.
|
||||
*/
|
||||
VOXELFORGE_API bool VF_AreConnected(
|
||||
VOXELFORGE_API EVoxelConnectivityResult VF_AreConnected(
|
||||
const UVoxelGenerator& Generator,
|
||||
const UVoxelStrateManager& Manager,
|
||||
int32 StrateIndex,
|
||||
const FVector& AVoxel,
|
||||
const FVector& BVoxel,
|
||||
const FVoxelStrateMeasureSettings& Settings,
|
||||
bool& bOutCoarseLied);
|
||||
bool& bOutStartSnapped,
|
||||
bool& bOutGoalSnapped);
|
||||
Reference in new issue
Block a user