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
+636
-150
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,
|
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,
|
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
|
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
|
`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
|
only that route at full voxel resolution before returning `Connected`. It returns an explicit
|
||||||
`bOutCoarseLied`. No UObject state, cache, actor, world, or PIE is required.
|
`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 |
|
| 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_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`
|
### 3.7 Cave morphology (SDF rooms/tunnels) — `Public/VoxelCaveMorphology.h` + `.cpp`
|
||||||
Header is rich with inline docs. Two namespaces + a per-chunk cache system.
|
Header is rich with inline docs. Two namespaces + a per-chunk cache system.
|
||||||
|
|||||||
@@ -25,16 +25,27 @@ namespace
|
|||||||
FVoxelStrateMetrics DerivedWindowRepeat;
|
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;
|
int32 Index = INDEX_NONE;
|
||||||
FString Name;
|
FString Name;
|
||||||
int32 UpperStrateIndex = INDEX_NONE;
|
int32 ArrivalPassageIndex = INDEX_NONE;
|
||||||
int32 LowerStrateIndex = INDEX_NONE;
|
int32 DeparturePassageIndex = INDEX_NONE;
|
||||||
FVector UpperPoint = FVector::ZeroVector;
|
FVector ArrivalPoint = FVector::ZeroVector;
|
||||||
FVector LowerPoint = FVector::ZeroVector;
|
FVector DeparturePoint = FVector::ZeroVector;
|
||||||
FString UpperResult;
|
bool bHasArrival = false;
|
||||||
FString LowerResult;
|
bool bHasDeparture = false;
|
||||||
|
FConnectivityProbe ArrivalToDeparture;
|
||||||
|
FConnectivityProbe ArrivalToLargest;
|
||||||
|
FConnectivityProbe DepartureToLargest;
|
||||||
};
|
};
|
||||||
|
|
||||||
FString ArchetypeName(const FStrateSlot& Slot)
|
FString ArchetypeName(const FStrateSlot& Slot)
|
||||||
@@ -55,6 +66,13 @@ namespace
|
|||||||
return FMemory::Memcmp(&A, &B, sizeof(float)) == 0;
|
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)
|
bool MetricsAreBitIdentical(const FVoxelStrateMetrics& A, const FVoxelStrateMetrics& B)
|
||||||
{
|
{
|
||||||
return A.bValid == B.bValid
|
return A.bValid == B.bValid
|
||||||
@@ -65,6 +83,9 @@ namespace
|
|||||||
&& SameFloatBits(A.AirFraction, B.AirFraction)
|
&& SameFloatBits(A.AirFraction, B.AirFraction)
|
||||||
&& A.NumAirComponents == B.NumAirComponents
|
&& A.NumAirComponents == B.NumAirComponents
|
||||||
&& SameFloatBits(A.LargestComponentShare, B.LargestComponentShare)
|
&& 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.WalkableFraction, B.WalkableFraction)
|
||||||
&& SameFloatBits(A.MedianFeatureScale, B.MedianFeatureScale)
|
&& SameFloatBits(A.MedianFeatureScale, B.MedianFeatureScale)
|
||||||
&& A.MedianVerticalClearance == B.MedianVerticalClearance
|
&& A.MedianVerticalClearance == B.MedianVerticalClearance
|
||||||
@@ -73,34 +94,45 @@ namespace
|
|||||||
&& A.SampledMaxZ == B.SampledMaxZ;
|
&& A.SampledMaxZ == B.SampledMaxZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsInsideMeasuredWindow(
|
const TCHAR* ConnectivityResultName(EVoxelConnectivityResult Result)
|
||||||
const FVoxelStrateMetrics& Metrics,
|
|
||||||
const FVoxelStrateMeasureSettings& Settings,
|
|
||||||
const FVector& Point)
|
|
||||||
{
|
{
|
||||||
const float MinX = Settings.CenterXY.X - static_cast<float>(Settings.RadiusInVoxels);
|
switch (Result)
|
||||||
const float MaxX = Settings.CenterXY.X + static_cast<float>(Settings.RadiusInVoxels);
|
{
|
||||||
const float MinY = Settings.CenterXY.Y - static_cast<float>(Settings.RadiusInVoxels);
|
case EVoxelConnectivityResult::Connected: return TEXT("CONNECTED");
|
||||||
const float MaxY = Settings.CenterXY.Y + static_cast<float>(Settings.RadiusInVoxels);
|
case EVoxelConnectivityResult::NotConnected: return TEXT("NOT_CONNECTED");
|
||||||
return Point.X >= MinX && Point.X < MaxX
|
case EVoxelConnectivityResult::StartCellSolid: return TEXT("START_CELL_SOLID");
|
||||||
&& Point.Y >= MinY && Point.Y < MaxY
|
case EVoxelConnectivityResult::GoalCellSolid: return TEXT("GOAL_CELL_SOLID");
|
||||||
&& Point.Z >= static_cast<float>(Metrics.SampledMinZ)
|
case EVoxelConnectivityResult::OutOfWindow: return TEXT("OUT_OF_WINDOW");
|
||||||
&& Point.Z < static_cast<float>(Metrics.SampledMaxZ);
|
case EVoxelConnectivityResult::CoarseLied: return TEXT("COARSE_LIED");
|
||||||
|
default: return TEXT("UNKNOWN");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FVector LargestComponentAnchor(
|
FString ConnectivityProbeText(const FConnectivityProbe& Probe)
|
||||||
const FStrateSlot& Slot,
|
|
||||||
const FVector2D& CenterXY,
|
|
||||||
int32 SampleStep)
|
|
||||||
{
|
{
|
||||||
// The fixture's origin spine is the deterministic hub/void anchor. The measurement API
|
if (!Probe.bChecked)
|
||||||
// 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.
|
return TEXT("NOT_CHECKED");
|
||||||
// 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
|
FString Text = ConnectivityResultName(Probe.Result);
|
||||||
+ 0.5f * static_cast<float>(SampleStep);
|
if (Probe.bStartSnapped || Probe.bGoalSnapped)
|
||||||
return FVector(CenterXY.X + 2.0f, CenterXY.Y + 2.0f, AnchorZ);
|
{
|
||||||
|
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(
|
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.
|
* 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.
|
* 3. Range sanity: fractions/components must describe an actual air field.
|
||||||
* 4. Determinism: identical inputs must produce bit-identical metrics.
|
* 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
|
* 5. Coarse-lie guard: a coarse route must never be reported CONNECTED after a
|
||||||
* solid sample; the controlled 3-voxel wall below must make bOutCoarseLied fire.
|
* full-resolution solid sample; the controlled 3-voxel wall below must return COARSE_LIED.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
int32 GapTopChunkZ = 0;
|
int32 GapTopChunkZ = 0;
|
||||||
@@ -349,6 +381,15 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
|||||||
|| !FMath::IsFinite(Metrics.WalkableFraction)
|
|| !FMath::IsFinite(Metrics.WalkableFraction)
|
||||||
|| Metrics.WalkableFraction < 0.0f || Metrics.WalkableFraction > 1.0f
|
|| Metrics.WalkableFraction < 0.0f || Metrics.WalkableFraction > 1.0f
|
||||||
|| (Metrics.NumAir > 0 && Metrics.NumAirComponents == 0)
|
|| (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.LargestComponentShare == 0.0f && Metrics.AirFraction > 0.05f)
|
||||||
|| Metrics.ResolvedMarginVoxels < 1
|
|| Metrics.ResolvedMarginVoxels < 1
|
||||||
|| Metrics.SampledMinZ >= Metrics.SampledMaxZ)
|
|| Metrics.SampledMinZ >= Metrics.SampledMaxZ)
|
||||||
@@ -408,20 +449,22 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
|||||||
GapAirSamples,
|
GapAirSamples,
|
||||||
GapSamples);
|
GapSamples);
|
||||||
Summary += TEXT("Archetypes (old one-chunk vs derived window; derived InteriorMarginVoxels<0 => 2x BoundarySealThickness, clamped):\n");
|
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)
|
for (const FArchetypeReport& Report : ArchetypeReports)
|
||||||
{
|
{
|
||||||
const FVoxelStrateMetrics& Old = Report.OldWindow;
|
const FVoxelStrateMetrics& Old = Report.OldWindow;
|
||||||
const FVoxelStrateMetrics& Derived = Report.DerivedWindow;
|
const FVoxelStrateMetrics& Derived = Report.DerivedWindow;
|
||||||
Summary += FString::Printf(
|
Summary += FString::Printf(
|
||||||
TEXT(" %s | %d | [%d,%d) | %.9g | %.9g | %.9g | %.9g | %d | %d | %lld | %lld | "
|
TEXT(" %s | %d | [%d,%d) | %.9g | %.9g | %lld | %d | %.9g | %.9g | %d | %d | %lld | %lld | "
|
||||||
"%d | [%d,%d) | %.9g | %.9g | %.9g | %.9g | %d | %d | %lld | %lld\n"),
|
"%d | [%d,%d) | %.9g | %.9g | %lld | %d | %.9g | %.9g | %d | %d | %lld | %lld\n"),
|
||||||
*Report.Name,
|
*Report.Name,
|
||||||
Old.ResolvedMarginVoxels,
|
Old.ResolvedMarginVoxels,
|
||||||
Old.SampledMinZ,
|
Old.SampledMinZ,
|
||||||
Old.SampledMaxZ,
|
Old.SampledMaxZ,
|
||||||
Old.AirFraction,
|
Old.AirFraction,
|
||||||
Old.LargestComponentShare,
|
Old.LargestComponentShare,
|
||||||
|
Old.LargestComponentCells,
|
||||||
|
Old.NumComponentsAtLeast1Pct,
|
||||||
Old.WalkableFraction,
|
Old.WalkableFraction,
|
||||||
Old.MedianFeatureScale,
|
Old.MedianFeatureScale,
|
||||||
Old.MedianVerticalClearance,
|
Old.MedianVerticalClearance,
|
||||||
@@ -433,6 +476,8 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
|||||||
Derived.SampledMaxZ,
|
Derived.SampledMaxZ,
|
||||||
Derived.AirFraction,
|
Derived.AirFraction,
|
||||||
Derived.LargestComponentShare,
|
Derived.LargestComponentShare,
|
||||||
|
Derived.LargestComponentCells,
|
||||||
|
Derived.NumComponentsAtLeast1Pct,
|
||||||
Derived.WalkableFraction,
|
Derived.WalkableFraction,
|
||||||
Derived.MedianFeatureScale,
|
Derived.MedianFeatureScale,
|
||||||
Derived.MedianVerticalClearance,
|
Derived.MedianVerticalClearance,
|
||||||
@@ -443,6 +488,81 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
|||||||
|
|
||||||
int32 NumRoutesChecked = 0;
|
int32 NumRoutesChecked = 0;
|
||||||
int32 NumRoutesRefuted = 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)
|
for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex)
|
||||||
{
|
{
|
||||||
const FStrateSlot& Slot = Layout[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,
|
const FVector B(Settings.CenterXY.X + 2.0f, Settings.CenterXY.Y + 2.0f,
|
||||||
static_cast<float>(Window.SampledMaxZ)
|
static_cast<float>(Window.SampledMaxZ)
|
||||||
- 0.5f * static_cast<float>(DerivedWindowSettings.SampleStep));
|
- 0.5f * static_cast<float>(DerivedWindowSettings.SampleStep));
|
||||||
bool bCoarseLied = false;
|
const FConnectivityProbe Probe = ProbeRoute(
|
||||||
const bool bConnected = VF_AreConnected(
|
StrateIndex, A, B, /*bCountInGuard=*/true, /*bCountAsAnchorProbe=*/false,
|
||||||
*World.Generator,
|
/*bCountAsArrivalDeparture=*/false);
|
||||||
*World.StrateManager,
|
if (Probe.Result == EVoxelConnectivityResult::Connected
|
||||||
StrateIndex,
|
&& FullResolutionDirectSegmentHasSolid(*World.Generator, A, B))
|
||||||
A,
|
|
||||||
B,
|
|
||||||
DerivedWindowSettings,
|
|
||||||
bCoarseLied);
|
|
||||||
++NumRoutesChecked;
|
|
||||||
if (bCoarseLied) ++NumRoutesRefuted;
|
|
||||||
if (bConnected && FullResolutionDirectSegmentHasSolid(*World.Generator, A, B))
|
|
||||||
{
|
{
|
||||||
AddError(FString::Printf(
|
AddError(FString::Printf(
|
||||||
TEXT("HARD FAILURE: VF_AreConnected returned true for %s while its full-resolution "
|
TEXT("HARD FAILURE: VF_AreConnected returned CONNECTED for %s while its "
|
||||||
"direct walk contained a solid sample."),
|
"full-resolution direct walk contained a solid sample."),
|
||||||
*ArchetypeName(Slot)));
|
*ArchetypeName(Slot)));
|
||||||
bAllChecksPassed = false;
|
bAllChecksPassed = false;
|
||||||
}
|
}
|
||||||
@@ -498,102 +611,315 @@ bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
|||||||
bAllChecksPassed = false;
|
bAllChecksPassed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool bWallCoarseLied = false;
|
const FConnectivityProbe WallProbe = ProbeRoute(
|
||||||
const bool bWallConnected = VF_AreConnected(
|
0, WallA, WallB, /*bCountInGuard=*/true, /*bCountAsAnchorProbe=*/false,
|
||||||
*World.Generator,
|
/*bCountAsArrivalDeparture=*/false);
|
||||||
*World.StrateManager,
|
if (WallProbe.Result != EVoxelConnectivityResult::CoarseLied)
|
||||||
0,
|
|
||||||
WallA,
|
|
||||||
WallB,
|
|
||||||
DerivedWindowSettings,
|
|
||||||
bWallCoarseLied);
|
|
||||||
++NumRoutesChecked;
|
|
||||||
if (bWallCoarseLied) ++NumRoutesRefuted;
|
|
||||||
if (bWallConnected)
|
|
||||||
{
|
{
|
||||||
AddError(TEXT("HARD FAILURE: VF_AreConnected reported true across a full-resolution solid 3-voxel wall."));
|
AddError(FString::Printf(
|
||||||
bAllChecksPassed = false;
|
TEXT("HARD FAILURE: the controlled coarse-connectivity lie returned %s instead of "
|
||||||
}
|
"COARSE_LIED."),
|
||||||
if (!bWallCoarseLied)
|
ConnectivityResultName(WallProbe.Result)));
|
||||||
{
|
|
||||||
AddError(TEXT("HARD FAILURE: the controlled coarse-connectivity lie did not set bOutCoarseLied."));
|
|
||||||
bAllChecksPassed = false;
|
bAllChecksPassed = false;
|
||||||
}
|
}
|
||||||
World.DiffLayer->Clear();
|
World.DiffLayer->Clear();
|
||||||
|
|
||||||
TArray<FPassageReport> PassageReports;
|
|
||||||
const TArray<FVoxelPassage>& Passages = World.StrateManager->GetPassages();
|
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)
|
for (int32 PassageIndex = 0; PassageIndex < Passages.Num(); ++PassageIndex)
|
||||||
{
|
{
|
||||||
const FVoxelPassage& Passage = Passages[PassageIndex];
|
const FVoxelPassage& Passage = Passages[PassageIndex];
|
||||||
FPassageReport& Report = PassageReports.AddDefaulted_GetRef();
|
if (Passage.UpperStrateIndex == Passage.LowerStrateIndex)
|
||||||
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 (!Layout.IsValidIndex(EndpointStrateIndex)) return TEXT("invalid-strate");
|
if (SurfaceEntryPassageIndex == INDEX_NONE)
|
||||||
const FVoxelStrateMetrics& EndpointMetrics =
|
|
||||||
DerivedMetricsByIndex[EndpointStrateIndex];
|
|
||||||
if (!EndpointMetrics.bValid) return TEXT("invalid-window");
|
|
||||||
if (!IsInsideMeasuredWindow(EndpointMetrics, DerivedWindowSettings, Endpoint))
|
|
||||||
{
|
{
|
||||||
return TEXT("out-of-window");
|
SurfaceEntryPassageIndex = PassageIndex;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FStrateSlot& Slot = Layout[EndpointStrateIndex];
|
if (Passage.LowerStrateIndex != Passage.UpperStrateIndex + 1)
|
||||||
const FVector Anchor = LargestComponentAnchor(
|
{
|
||||||
Slot,
|
AddError(FString::Printf(
|
||||||
DerivedWindowSettings.CenterXY,
|
TEXT("HARD FAILURE: passage %d is not between consecutive strates (%d -> %d)."),
|
||||||
DerivedWindowSettings.SampleStep);
|
PassageIndex,
|
||||||
bool bCoarseLied = false;
|
Passage.UpperStrateIndex,
|
||||||
const bool bConnected = VF_AreConnected(
|
Passage.LowerStrateIndex));
|
||||||
*World.Generator,
|
bAllChecksPassed = false;
|
||||||
*World.StrateManager,
|
continue;
|
||||||
EndpointStrateIndex,
|
|
||||||
Endpoint,
|
|
||||||
Anchor,
|
|
||||||
DerivedWindowSettings,
|
|
||||||
bCoarseLied);
|
|
||||||
++NumRoutesChecked;
|
|
||||||
if (bCoarseLied) ++NumRoutesRefuted;
|
|
||||||
return bConnected ? TEXT("CONNECTED_TO_LARGEST") : TEXT("NOT_CONNECTED");
|
|
||||||
};
|
|
||||||
|
|
||||||
Report.UpperResult = CheckEndpoint(Passage.UpperStrateIndex, Passage.UpperPoint);
|
|
||||||
Report.LowerResult = CheckEndpoint(Passage.LowerStrateIndex, Passage.LowerPoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PassageReports.Sort([](const FPassageReport& A, const FPassageReport& B)
|
if (!Layout.IsValidIndex(Passage.UpperStrateIndex)
|
||||||
|
|| !Layout.IsValidIndex(Passage.LowerStrateIndex))
|
||||||
{
|
{
|
||||||
return A.Index < B.Index;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex)
|
||||||
|
{
|
||||||
|
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(
|
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,
|
NumRoutesChecked,
|
||||||
NumRoutesRefuted);
|
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(
|
Summary += FString::Printf(
|
||||||
TEXT(" passage %s: UpperPoint strate=%d (%.3f,%.3f,%.3f) => %s; "
|
TEXT("Endpoint snap repair: %d snapped endpoint events across all probes; %d in "
|
||||||
"LowerPoint strate=%d (%.3f,%.3f,%.3f) => %s\n"),
|
"%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(" %d %s | %s | %s | %s | %d | %.9g | %lld | %d | (%.3f,%.3f,%.3f)\n"),
|
||||||
|
Report.Index,
|
||||||
*Report.Name,
|
*Report.Name,
|
||||||
Report.UpperStrateIndex,
|
*ArrivalToDeparture,
|
||||||
Report.UpperPoint.X,
|
*ArrivalToLargest,
|
||||||
Report.UpperPoint.Y,
|
*DepartureToLargest,
|
||||||
Report.UpperPoint.Z,
|
Metrics.NumAirComponents,
|
||||||
*Report.UpperResult,
|
Metrics.LargestComponentShare,
|
||||||
Report.LowerStrateIndex,
|
Metrics.LargestComponentCells,
|
||||||
Report.LowerPoint.X,
|
Metrics.NumComponentsAtLeast1Pct,
|
||||||
Report.LowerPoint.Y,
|
Metrics.LargestComponentPoint.X,
|
||||||
Report.LowerPoint.Z,
|
Metrics.LargestComponentPoint.Y,
|
||||||
*Report.LowerResult);
|
Metrics.LargestComponentPoint.Z);
|
||||||
}
|
}
|
||||||
AddInfo(Summary);
|
AddInfo(Summary);
|
||||||
|
|
||||||
|
|||||||
@@ -298,7 +298,10 @@ namespace VoxelStrateMeasurePrivate
|
|||||||
const FSampleGrid& Grid,
|
const FSampleGrid& Grid,
|
||||||
TArray<int32>& OutComponents,
|
TArray<int32>& OutComponents,
|
||||||
int32& OutNumComponents,
|
int32& OutNumComponents,
|
||||||
int32& OutLargestComponentCells)
|
int64 NumAir,
|
||||||
|
int64& OutLargestComponentCells,
|
||||||
|
int32& OutLargestComponentLowestCell,
|
||||||
|
int32& OutNumComponentsAtLeast1Pct)
|
||||||
{
|
{
|
||||||
OutComponents.SetNumUninitialized(Grid.CellCount);
|
OutComponents.SetNumUninitialized(Grid.CellCount);
|
||||||
for (int32 Index = 0; Index < Grid.CellCount; ++Index)
|
for (int32 Index = 0; Index < Grid.CellCount; ++Index)
|
||||||
@@ -310,6 +313,8 @@ namespace VoxelStrateMeasurePrivate
|
|||||||
Queue.SetNumUninitialized(Grid.CellCount);
|
Queue.SetNumUninitialized(Grid.CellCount);
|
||||||
OutNumComponents = 0;
|
OutNumComponents = 0;
|
||||||
OutLargestComponentCells = 0;
|
OutLargestComponentCells = 0;
|
||||||
|
OutLargestComponentLowestCell = INDEX_NONE;
|
||||||
|
OutNumComponentsAtLeast1Pct = 0;
|
||||||
|
|
||||||
for (int32 Start = 0; Start < Grid.CellCount; ++Start)
|
for (int32 Start = 0; Start < Grid.CellCount; ++Start)
|
||||||
{
|
{
|
||||||
@@ -353,7 +358,20 @@ namespace VoxelStrateMeasurePrivate
|
|||||||
if (Z > 0) Visit(Grid.Index(X, Y, Z - 1));
|
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;
|
TArray<int32> Components;
|
||||||
int32 NumComponents = 0;
|
int32 NumComponents = 0;
|
||||||
int32 LargestComponentCells = 0;
|
|
||||||
FloodFillAir(Grid, Components, NumComponents, LargestComponentCells);
|
|
||||||
|
|
||||||
int64 NumAir = 0;
|
int64 NumAir = 0;
|
||||||
for (const uint8 bAir : Grid.Air)
|
for (const uint8 bAir : Grid.Air)
|
||||||
@@ -438,16 +454,38 @@ namespace VoxelStrateMeasurePrivate
|
|||||||
NumAir += bAir != 0u ? 1 : 0;
|
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.NumSampled = Grid.CellCount;
|
||||||
InOutMetrics.NumAir = NumAir;
|
InOutMetrics.NumAir = NumAir;
|
||||||
InOutMetrics.NumSolid = static_cast<int64>(Grid.CellCount) - NumAir;
|
InOutMetrics.NumSolid = static_cast<int64>(Grid.CellCount) - NumAir;
|
||||||
InOutMetrics.AirFraction = static_cast<float>(
|
InOutMetrics.AirFraction = static_cast<float>(
|
||||||
static_cast<double>(NumAir) / static_cast<double>(Grid.CellCount));
|
static_cast<double>(NumAir) / static_cast<double>(Grid.CellCount));
|
||||||
InOutMetrics.NumAirComponents = NumComponents;
|
InOutMetrics.NumAirComponents = NumComponents;
|
||||||
|
InOutMetrics.LargestComponentCells = LargestComponentCells;
|
||||||
|
InOutMetrics.NumComponentsAtLeast1Pct = NumComponentsAtLeast1Pct;
|
||||||
InOutMetrics.LargestComponentShare = NumAir > 0
|
InOutMetrics.LargestComponentShare = NumAir > 0
|
||||||
? static_cast<float>(static_cast<double>(LargestComponentCells)
|
? static_cast<float>(static_cast<double>(LargestComponentCells)
|
||||||
/ static_cast<double>(NumAir))
|
/ static_cast<double>(NumAir))
|
||||||
: 0.0f;
|
: 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;
|
TArray<int32> ClearanceValues;
|
||||||
ClearanceValues.Reserve(static_cast<int32>(NumAir));
|
ClearanceValues.Reserve(static_cast<int32>(NumAir));
|
||||||
@@ -537,6 +575,93 @@ namespace VoxelStrateMeasurePrivate
|
|||||||
return true;
|
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(
|
bool FullResolutionAirOnSegment(
|
||||||
const UVoxelGenerator& Generator,
|
const UVoxelGenerator& Generator,
|
||||||
const FVector& Start,
|
const FVector& Start,
|
||||||
@@ -706,20 +831,22 @@ FVoxelStrateMetrics VF_MeasureStrate(
|
|||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VF_AreConnected(
|
EVoxelConnectivityResult VF_AreConnected(
|
||||||
const UVoxelGenerator& Generator,
|
const UVoxelGenerator& Generator,
|
||||||
const UVoxelStrateManager& Manager,
|
const UVoxelStrateManager& Manager,
|
||||||
int32 StrateIndex,
|
int32 StrateIndex,
|
||||||
const FVector& AVoxel,
|
const FVector& AVoxel,
|
||||||
const FVector& BVoxel,
|
const FVector& BVoxel,
|
||||||
const FVoxelStrateMeasureSettings& Settings,
|
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)
|
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))
|
|| !FMath::IsFinite(BVoxel.X) || !FMath::IsFinite(BVoxel.Y) || !FMath::IsFinite(BVoxel.Z))
|
||||||
{
|
{
|
||||||
return false;
|
return EVoxelConnectivityResult::OutOfWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
VoxelStrateMeasurePrivate::FSampleGrid Grid;
|
VoxelStrateMeasurePrivate::FSampleGrid Grid;
|
||||||
@@ -727,21 +854,35 @@ bool VF_AreConnected(
|
|||||||
if (!VoxelStrateMeasurePrivate::BuildSampleGrid(
|
if (!VoxelStrateMeasurePrivate::BuildSampleGrid(
|
||||||
Generator, Manager, StrateIndex, Settings, Grid, RefusalReason))
|
Generator, Manager, StrateIndex, Settings, Grid, RefusalReason))
|
||||||
{
|
{
|
||||||
return false;
|
return EVoxelConnectivityResult::OutOfWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 Start = -1;
|
int32 Start = -1;
|
||||||
int32 Goal = -1;
|
int32 Goal = -1;
|
||||||
if (!VoxelStrateMeasurePrivate::FindCellForPoint(Grid, AVoxel, Start)
|
const VoxelStrateMeasurePrivate::EEndpointCellResult StartCell =
|
||||||
|| !VoxelStrateMeasurePrivate::FindCellForPoint(Grid, BVoxel, Goal))
|
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;
|
TArray<int32> Path;
|
||||||
if (!VoxelStrateMeasurePrivate::FindCoarsePath(Grid, Start, Goal, 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
|
// 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(
|
if (!VoxelStrateMeasurePrivate::FullResolutionPathIsAir(
|
||||||
Generator, Grid, AVoxel, BVoxel, Path))
|
Generator, Grid, AVoxel, BVoxel, Path))
|
||||||
{
|
{
|
||||||
bOutCoarseLied = true;
|
return EVoxelConnectivityResult::CoarseLied;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return EVoxelConnectivityResult::Connected;
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,17 @@
|
|||||||
class UVoxelGenerator;
|
class UVoxelGenerator;
|
||||||
class UVoxelStrateManager;
|
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. */
|
/** Settings for the deterministic coarse grid used by the strate measurement pass. */
|
||||||
struct VOXELFORGE_API FVoxelStrateMeasureSettings
|
struct VOXELFORGE_API FVoxelStrateMeasureSettings
|
||||||
{
|
{
|
||||||
@@ -31,6 +42,9 @@ struct VOXELFORGE_API FVoxelStrateMetrics
|
|||||||
float AirFraction = 0.f;
|
float AirFraction = 0.f;
|
||||||
int32 NumAirComponents = 0;
|
int32 NumAirComponents = 0;
|
||||||
float LargestComponentShare = 0.f;
|
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 WalkableFraction = 0.f;
|
||||||
float MedianFeatureScale = 0.f;
|
float MedianFeatureScale = 0.f;
|
||||||
int32 MedianVerticalClearance = 0;
|
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.
|
* 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
|
* If an endpoint's own coarse cell is solid, the query searches the 26 neighbouring cells and
|
||||||
* route was solid (or non-finite), in which case this function always returns false.
|
* 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 UVoxelGenerator& Generator,
|
||||||
const UVoxelStrateManager& Manager,
|
const UVoxelStrateManager& Manager,
|
||||||
int32 StrateIndex,
|
int32 StrateIndex,
|
||||||
const FVector& AVoxel,
|
const FVector& AVoxel,
|
||||||
const FVector& BVoxel,
|
const FVector& BVoxel,
|
||||||
const FVoxelStrateMeasureSettings& Settings,
|
const FVoxelStrateMeasureSettings& Settings,
|
||||||
bool& bOutCoarseLied);
|
bool& bOutStartSnapped,
|
||||||
|
bool& bOutGoalSnapped);
|
||||||
Reference in new issue
Block a user