test(measure): the spine is the backbone in 7 of 8 strates -- and not in
VerticalShafts Diagnosis only. No generation or placement changed. The hypothesis was that the (0,0) spine (OriginSpineRadius 14) is the universal connector, so "can this mouth reach the spine" would be the real primordial-law test. Measured at margin 0, step 2, radius 192: strate spine share largest? arr->spine dep->spine TunnelNetwork 0.979 YES CONNECTED not connected FlatPlain 1.000 YES CONNECTED CONNECTED CrystalChamber 1.000 YES CONNECTED CONNECTED Maze 0.997 YES coarse-lied CONNECTED SurfaceWorld 1.000 YES CONNECTED CONNECTED VerticalShafts 0.096 NO not conn. not connected FloatingIslands 1.000 YES CONNECTED CONNECTED Underwater 0.999 YES CONNECTED n/a The spine forms one uninterrupted open interior run in 8/8 strates, and holds essentially ALL the air in 7 of them. VerticalShafts is the lone anomaly: the spine punches through as an isolated column holding 9.6% of the air and is NOT the largest component. So the archetype has three mutually unreachable things -- the spine, the arrival network, the departure network. It has no lateral connectivity at all. Over 16 seeds: spine open 16/16, spine largest only 8/16, arrival->spine 1 connected, departure->spine 0 connected. Neither extreme was right. The spine is not a universal connector, but it is far from irrelevant: it is the de-facto backbone everywhere except here, which reframes the bug as an ANOMALY to bring in line rather than a missing feature to invent. Ordering hazard confirmed on the record: making passage i depend on passage i-1 would break LayoutOrderIndependence. Both candidate fixes avoid it, because the spine is a fixed deterministic anchor that two mouths can target independently. Build clean, 19 succeeded / 0 warnings / 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bp7FxoQcSoiuhxjuLr6aBM
This commit is contained in:
1 parent
0bbfdeb7a7
commit
5519b323c8
4 files changed
+903
-146
No files matched your search
+2
-2
@@ -320,7 +320,7 @@ or PIE is required.
|
|||||||
|--------|------|
|
|--------|------|
|
||||||
| `FVoxelStrateMeasureSettings` / `FVoxelStrateMetrics` | Plain settings/result structs for bounded strate sampling and derived measurements; callers may override the interior margin (including zero to sample the seal), and results identify the resolved margin/Z window and deterministic component representatives. |
|
| `FVoxelStrateMeasureSettings` / `FVoxelStrateMetrics` | Plain settings/result structs for bounded strate sampling and derived measurements; callers may override the interior margin (including zero to sample the seal), 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, with explicit endpoint-solid, out-of-window, `CoarseLied`, `NotConnectedAtThisResolution`, and connected outcomes. |
|
| `VF_AreConnected` | Coarse 6-connected BFS plus one full-resolution recheck of its recovered route, with explicit endpoint-solid, out-of-window, `CoarseLied`, `NotConnectedAtThisResolution`, and connected outcomes; the diagnostics overload returns endpoint component facts from that same grid. |
|
||||||
| `VF_DiagnoseConnectivity` | The same query plus each mouth's air-component size/share and exact geometric distance to the nearest cell of the other component; proximity is measured in coarse-cell coordinates and does not claim a route through solid. |
|
| `VF_DiagnoseConnectivity` | The same query plus each mouth's air-component size/share and exact geometric distance to the nearest cell of the other component; proximity is measured in coarse-cell coordinates and does not claim a route through solid. |
|
||||||
|
|
||||||
### 3.7 Cave morphology (SDF rooms/tunnels) — `Public/VoxelCaveMorphology.h` + `.cpp`
|
### 3.7 Cave morphology (SDF rooms/tunnels) — `Public/VoxelCaveMorphology.h` + `.cpp`
|
||||||
@@ -486,7 +486,7 @@ The plugin's first tests (`OPSTACK-PLAN.md` Phase 0.5). Run them from the editor
|
|||||||
| `VoxelForgeStrateParamCoverageTest.cpp` | `VoxelForge.Determinism.StrateParamBlendCoverage` | **The X-macro guard** (added 2026-08-17). `FStrateGenerationParams::Lerp` blends the hand-written `VF_STRATE_PARAM_FIELDS` list, **not** the struct — so a field added to one and not the other compiles, tests green, and silently takes its **default** inside every Gradient/Interleaved transition band. This expands the X-macro a **third** way (after LERP and SNAP), into a name list, and diffs it against the struct's UObject reflection. Pure shape test: no fixture, no world, instant. `GExemptFieldNames` is **empty** — every reflected field is covered today, and any exemption must be written down as a decision. Stakes rise with the world composer, which intends to invent parameter sets through this same `Lerp` (`COMPOSER-NOTES.md`). |
|
| `VoxelForgeStrateParamCoverageTest.cpp` | `VoxelForge.Determinism.StrateParamBlendCoverage` | **The X-macro guard** (added 2026-08-17). `FStrateGenerationParams::Lerp` blends the hand-written `VF_STRATE_PARAM_FIELDS` list, **not** the struct — so a field added to one and not the other compiles, tests green, and silently takes its **default** inside every Gradient/Interleaved transition band. This expands the X-macro a **third** way (after LERP and SNAP), into a name list, and diffs it against the struct's UObject reflection. Pure shape test: no fixture, no world, instant. `GExemptFieldNames` is **empty** — every reflected field is covered today, and any exemption must be written down as a decision. Stakes rise with the world composer, which intends to invent parameter sets through this same `Lerp` (`COMPOSER-NOTES.md`). |
|
||||||
| `VoxelForgeLayoutOrderIndependenceTest.cpp` | `VoxelForge.Determinism.LayoutOrderIndependence` | Builds a known transient soft-pointer pool, then rebuilds it in original, reversed, and swapped orders. Requires a non-empty layout and passage set, and compares every slot's definition/Z/height plus passage endpoints, radius, type, control geometry, and bounds bit-for-bit. |
|
| `VoxelForgeLayoutOrderIndependenceTest.cpp` | `VoxelForge.Determinism.LayoutOrderIndependence` | Builds a known transient soft-pointer pool, then rebuilds it in original, reversed, and swapped orders. Requires a non-empty layout and passage set, and compares every slot's definition/Z/height plus passage endpoints, radius, type, control geometry, and bounds bit-for-bit. |
|
||||||
| `VoxelForgePassageOpenSpaceTest.cpp` | `VoxelForge.Determinism.PassageLandsInOpenSpace` | Uses the real fixture density path to check every generated inter-strate passage whose destination query answers: a 16-point ring outside the mouth's carve/blend band has at least half its samples in destination air, and the endpoint matches the pure open-point result within the mouth's float envelope. This is a connectivity proxy, not a flood-fill proof. Reports checked passages and false/unanswerable archetypes; fails if it inspects zero passages. |
|
| `VoxelForgePassageOpenSpaceTest.cpp` | `VoxelForge.Determinism.PassageLandsInOpenSpace` | Uses the real fixture density path to check every generated inter-strate passage whose destination query answers: a 16-point ring outside the mouth's carve/blend band has at least half its samples in destination air, and the endpoint matches the pure open-point result within the mouth's float envelope. This is a connectivity proxy, not a flood-fill proof. Reports checked passages and false/unanswerable archetypes; fails if it inspects zero passages. |
|
||||||
| `VoxelForgeStrateConnectivityTest.cpp` | `VoxelForge.Generation.StrateConnectivity` / `VoxelForge.Generation.StrateConnectivityRefinement` | Bounded strate metrics with density-polarity and solid-gap controls, deterministic route rechecks, and a VerticalShafts refinement sweep. The refinement test explicitly checks margins 0/2/4/8 at step 2/radius 192, then conditionally reports mouth components/proximity and a 16-seed arrival→departure result. |
|
| `VoxelForgeStrateConnectivityTest.cpp` | `VoxelForge.Generation.StrateConnectivity` / `VoxelForge.Generation.StrateConnectivityRefinement` | Bounded strate metrics with density-polarity and solid-gap controls, deterministic route rechecks, and refinement sweeps. The refinement test explicitly checks margins 0/2/4/8 at step 2/radius 192, reports the `(0,0)` spine's full-window/interior vertical run and component for all eight fixture strates plus arrival/departure→spine and arrival→departure enums, then repeats those spine/mouth measurements across 16 VerticalShafts seeds. |
|
||||||
|
|
||||||
## 4. The density pipeline (most-edited hot path)
|
## 4. The density pipeline (most-edited hot path)
|
||||||
|
|
||||||
|
|||||||
@@ -276,6 +276,341 @@ namespace
|
|||||||
}
|
}
|
||||||
return ArrivalCount == 1 && DepartureCount == 1;
|
return ArrivalCount == 1 && DepartureCount == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FSpineColumnReport
|
||||||
|
{
|
||||||
|
bool bValid = false;
|
||||||
|
int32 SampledMinZ = 0;
|
||||||
|
int32 SampledMaxZ = 0;
|
||||||
|
int64 NumSamples = 0;
|
||||||
|
int64 NumAir = 0;
|
||||||
|
float AirFraction = 0.0f;
|
||||||
|
bool bFullWindowRun = false;
|
||||||
|
bool bAirSamplesFormOneRun = false;
|
||||||
|
int32 FirstOpenZ = INDEX_NONE;
|
||||||
|
int32 LastOpenZ = INDEX_NONE;
|
||||||
|
int32 LongestOpenRun = 0;
|
||||||
|
int32 RepresentativeOpenZ = INDEX_NONE;
|
||||||
|
TArray<uint8> AirByZ;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool SampleOriginSpineColumn(
|
||||||
|
const UVoxelGenerator& Generator,
|
||||||
|
int32 SampledMinZ,
|
||||||
|
int32 SampledMaxZ,
|
||||||
|
FSpineColumnReport& OutReport)
|
||||||
|
{
|
||||||
|
OutReport = FSpineColumnReport();
|
||||||
|
if (SampledMaxZ <= SampledMinZ)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int64 NumSamples64 = static_cast<int64>(SampledMaxZ)
|
||||||
|
- static_cast<int64>(SampledMinZ);
|
||||||
|
if (NumSamples64 <= 0 || NumSamples64 > INT32_MAX)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutReport.SampledMinZ = SampledMinZ;
|
||||||
|
OutReport.SampledMaxZ = SampledMaxZ;
|
||||||
|
OutReport.NumSamples = NumSamples64;
|
||||||
|
OutReport.AirByZ.SetNumUninitialized(static_cast<int32>(NumSamples64));
|
||||||
|
|
||||||
|
bool bSawNonFiniteDensity = false;
|
||||||
|
int32 CurrentRun = 0;
|
||||||
|
for (int32 SampleIndex = 0; SampleIndex < OutReport.AirByZ.Num(); ++SampleIndex)
|
||||||
|
{
|
||||||
|
const int32 Z = SampledMinZ + SampleIndex;
|
||||||
|
const float Density = Generator.GetDensityAt(0.0f, 0.0f, static_cast<float>(Z));
|
||||||
|
const bool bAir = FMath::IsFinite(Density) && Density > 0.0f;
|
||||||
|
OutReport.AirByZ[SampleIndex] = bAir ? 1u : 0u;
|
||||||
|
if (!FMath::IsFinite(Density))
|
||||||
|
{
|
||||||
|
bSawNonFiniteDensity = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bAir)
|
||||||
|
{
|
||||||
|
++OutReport.NumAir;
|
||||||
|
++CurrentRun;
|
||||||
|
OutReport.FirstOpenZ = OutReport.FirstOpenZ == INDEX_NONE
|
||||||
|
? Z : OutReport.FirstOpenZ;
|
||||||
|
OutReport.LastOpenZ = Z;
|
||||||
|
OutReport.LongestOpenRun = FMath::Max(OutReport.LongestOpenRun, CurrentRun);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CurrentRun = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OutReport.AirFraction = static_cast<float>(
|
||||||
|
static_cast<double>(OutReport.NumAir)
|
||||||
|
/ static_cast<double>(OutReport.NumSamples));
|
||||||
|
OutReport.bFullWindowRun = OutReport.NumAir == OutReport.NumSamples;
|
||||||
|
OutReport.bAirSamplesFormOneRun = OutReport.NumAir > 0
|
||||||
|
&& OutReport.LongestOpenRun == OutReport.NumAir;
|
||||||
|
OutReport.bValid = !bSawNonFiniteDensity;
|
||||||
|
|
||||||
|
if (OutReport.NumAir > 0)
|
||||||
|
{
|
||||||
|
const int32 CentreZ = SampledMinZ
|
||||||
|
+ static_cast<int32>(NumSamples64 / 2);
|
||||||
|
for (int32 Offset = 0; Offset < OutReport.AirByZ.Num(); ++Offset)
|
||||||
|
{
|
||||||
|
const int32 LowerZ = CentreZ - Offset;
|
||||||
|
if (LowerZ >= SampledMinZ && LowerZ < SampledMaxZ
|
||||||
|
&& OutReport.AirByZ[LowerZ - SampledMinZ] != 0u)
|
||||||
|
{
|
||||||
|
OutReport.RepresentativeOpenZ = LowerZ;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 UpperZ = CentreZ + Offset;
|
||||||
|
if (UpperZ >= SampledMinZ && UpperZ < SampledMaxZ
|
||||||
|
&& OutReport.AirByZ[UpperZ - SampledMinZ] != 0u)
|
||||||
|
{
|
||||||
|
OutReport.RepresentativeOpenZ = UpperZ;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return OutReport.bValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FindNearestOpenSpinePoint(
|
||||||
|
const UVoxelGenerator& Generator,
|
||||||
|
const FSpineColumnReport& Spine,
|
||||||
|
float TargetZ,
|
||||||
|
FVector& OutPoint)
|
||||||
|
{
|
||||||
|
if (!Spine.bValid || Spine.NumAir <= 0 || !FMath::IsFinite(TargetZ))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TargetZ >= static_cast<float>(Spine.SampledMinZ)
|
||||||
|
&& TargetZ < static_cast<float>(Spine.SampledMaxZ))
|
||||||
|
{
|
||||||
|
const float Density = Generator.GetDensityAt(0.0f, 0.0f, TargetZ);
|
||||||
|
if (FMath::IsFinite(Density) && Density > 0.0f)
|
||||||
|
{
|
||||||
|
OutPoint = FVector(0.0f, 0.0f, TargetZ);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 NearestZ = FMath::Clamp(
|
||||||
|
FMath::RoundToInt(TargetZ), Spine.SampledMinZ, Spine.SampledMaxZ - 1);
|
||||||
|
for (int32 Offset = 0; Offset < Spine.AirByZ.Num(); ++Offset)
|
||||||
|
{
|
||||||
|
const int32 LowerZ = NearestZ - Offset;
|
||||||
|
if (LowerZ >= Spine.SampledMinZ && LowerZ < Spine.SampledMaxZ
|
||||||
|
&& Spine.AirByZ[LowerZ - Spine.SampledMinZ] != 0u)
|
||||||
|
{
|
||||||
|
OutPoint = FVector(0.0f, 0.0f, static_cast<float>(LowerZ));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 UpperZ = NearestZ + Offset;
|
||||||
|
if (UpperZ >= Spine.SampledMinZ && UpperZ < Spine.SampledMaxZ
|
||||||
|
&& Spine.AirByZ[UpperZ - Spine.SampledMinZ] != 0u)
|
||||||
|
{
|
||||||
|
OutPoint = FVector(0.0f, 0.0f, static_cast<float>(UpperZ));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FSpineDiagnosisReport
|
||||||
|
{
|
||||||
|
int32 Seed = INDEX_NONE;
|
||||||
|
int32 Index = INDEX_NONE;
|
||||||
|
FString Name;
|
||||||
|
FSpineColumnReport Spine;
|
||||||
|
bool bHasArrival = false;
|
||||||
|
bool bArrivalIsSurfaceEntry = false;
|
||||||
|
bool bHasChainArrival = false;
|
||||||
|
bool bHasDeparture = false;
|
||||||
|
FVector ArrivalPoint = FVector::ZeroVector;
|
||||||
|
FVector DeparturePoint = FVector::ZeroVector;
|
||||||
|
FVector ArrivalSpinePoint = FVector::ZeroVector;
|
||||||
|
FVector DepartureSpinePoint = FVector::ZeroVector;
|
||||||
|
FVector RepresentativeSpinePoint = FVector::ZeroVector;
|
||||||
|
FConnectivityProbe SpineComponentProbe;
|
||||||
|
FConnectivityProbe ArrivalToSpine;
|
||||||
|
FConnectivityProbe DepartureToSpine;
|
||||||
|
FConnectivityProbe ArrivalToDeparture;
|
||||||
|
FVoxelConnectivityDiagnostics SpineComponent;
|
||||||
|
FVoxelConnectivityDiagnostics ArrivalToSpineFacts;
|
||||||
|
FVoxelConnectivityDiagnostics DepartureToSpineFacts;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool FindDiagnosticMouths(
|
||||||
|
const TArray<FVoxelPassage>& Passages,
|
||||||
|
int32 StrateIndex,
|
||||||
|
FSpineDiagnosisReport& OutReport)
|
||||||
|
{
|
||||||
|
int32 ChainArrivalCount = 0;
|
||||||
|
int32 SurfaceArrivalCount = 0;
|
||||||
|
int32 DepartureCount = 0;
|
||||||
|
for (const FVoxelPassage& Passage : Passages)
|
||||||
|
{
|
||||||
|
if (Passage.LowerStrateIndex == StrateIndex
|
||||||
|
&& Passage.UpperStrateIndex + 1 == StrateIndex)
|
||||||
|
{
|
||||||
|
OutReport.ArrivalPoint = Passage.LowerPoint;
|
||||||
|
++ChainArrivalCount;
|
||||||
|
}
|
||||||
|
if (StrateIndex == 0
|
||||||
|
&& Passage.UpperStrateIndex == 0
|
||||||
|
&& Passage.LowerStrateIndex == 0)
|
||||||
|
{
|
||||||
|
OutReport.ArrivalPoint = Passage.LowerPoint;
|
||||||
|
++SurfaceArrivalCount;
|
||||||
|
}
|
||||||
|
if (Passage.UpperStrateIndex == StrateIndex
|
||||||
|
&& Passage.LowerStrateIndex == StrateIndex + 1)
|
||||||
|
{
|
||||||
|
OutReport.DeparturePoint = Passage.UpperPoint;
|
||||||
|
++DepartureCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ChainArrivalCount > 1 || SurfaceArrivalCount > 1 || DepartureCount > 1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (StrateIndex == 0)
|
||||||
|
{
|
||||||
|
OutReport.bHasArrival = SurfaceArrivalCount == 1;
|
||||||
|
OutReport.bArrivalIsSurfaceEntry = OutReport.bHasArrival;
|
||||||
|
OutReport.bHasChainArrival = ChainArrivalCount == 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OutReport.bHasArrival = ChainArrivalCount == 1;
|
||||||
|
OutReport.bHasChainArrival = OutReport.bHasArrival;
|
||||||
|
}
|
||||||
|
OutReport.bHasDeparture = DepartureCount == 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BuildSpineDiagnosisReport(
|
||||||
|
const UVoxelGenerator& Generator,
|
||||||
|
const UVoxelStrateManager& Manager,
|
||||||
|
int32 StrateIndex,
|
||||||
|
const FVoxelStrateMeasureSettings& Settings,
|
||||||
|
FSpineDiagnosisReport& OutReport)
|
||||||
|
{
|
||||||
|
OutReport = FSpineDiagnosisReport();
|
||||||
|
OutReport.Index = StrateIndex;
|
||||||
|
const TArray<FStrateSlot>& Layout = Manager.GetLayout();
|
||||||
|
if (!Layout.IsValidIndex(StrateIndex) || Layout[StrateIndex].Definition == nullptr)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
OutReport.Name = ArchetypeName(Layout[StrateIndex]);
|
||||||
|
|
||||||
|
if (Settings.InteriorMarginVoxels != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const int64 MinZ64 = static_cast<int64>(Layout[StrateIndex].BottomChunkZ) * CHUNK_SIZE;
|
||||||
|
const int64 MaxZ64 = (static_cast<int64>(Layout[StrateIndex].TopChunkZ) + 1) * CHUNK_SIZE;
|
||||||
|
if (MinZ64 < INT32_MIN || MinZ64 > INT32_MAX
|
||||||
|
|| MaxZ64 < INT32_MIN || MaxZ64 > INT32_MAX)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!SampleOriginSpineColumn(
|
||||||
|
Generator,
|
||||||
|
static_cast<int32>(MinZ64),
|
||||||
|
static_cast<int32>(MaxZ64),
|
||||||
|
OutReport.Spine))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OutReport.Spine.RepresentativeOpenZ != INDEX_NONE)
|
||||||
|
{
|
||||||
|
OutReport.RepresentativeSpinePoint = FVector(
|
||||||
|
0.0f, 0.0f, static_cast<float>(OutReport.Spine.RepresentativeOpenZ));
|
||||||
|
OutReport.SpineComponentProbe.bChecked = true;
|
||||||
|
OutReport.SpineComponentProbe.Result = VF_AreConnected(
|
||||||
|
Generator,
|
||||||
|
Manager,
|
||||||
|
StrateIndex,
|
||||||
|
OutReport.RepresentativeSpinePoint,
|
||||||
|
OutReport.RepresentativeSpinePoint,
|
||||||
|
Settings,
|
||||||
|
OutReport.SpineComponentProbe.bStartSnapped,
|
||||||
|
OutReport.SpineComponentProbe.bGoalSnapped,
|
||||||
|
OutReport.SpineComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FindDiagnosticMouths(Manager.GetPassages(), StrateIndex, OutReport))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ProbeMouthToSpine = [&](const FVector& Mouth,
|
||||||
|
FVector& OutSpinePoint,
|
||||||
|
FConnectivityProbe& OutProbe,
|
||||||
|
FVoxelConnectivityDiagnostics& OutFacts)
|
||||||
|
{
|
||||||
|
if (!FindNearestOpenSpinePoint(Generator, OutReport.Spine, Mouth.Z, OutSpinePoint))
|
||||||
|
{
|
||||||
|
OutSpinePoint = FVector(0.0f, 0.0f, Mouth.Z);
|
||||||
|
}
|
||||||
|
OutProbe.bChecked = true;
|
||||||
|
OutProbe.Result = VF_AreConnected(
|
||||||
|
Generator,
|
||||||
|
Manager,
|
||||||
|
StrateIndex,
|
||||||
|
Mouth,
|
||||||
|
OutSpinePoint,
|
||||||
|
Settings,
|
||||||
|
OutProbe.bStartSnapped,
|
||||||
|
OutProbe.bGoalSnapped,
|
||||||
|
OutFacts);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (OutReport.bHasArrival)
|
||||||
|
{
|
||||||
|
ProbeMouthToSpine(
|
||||||
|
OutReport.ArrivalPoint,
|
||||||
|
OutReport.ArrivalSpinePoint,
|
||||||
|
OutReport.ArrivalToSpine,
|
||||||
|
OutReport.ArrivalToSpineFacts);
|
||||||
|
}
|
||||||
|
if (OutReport.bHasDeparture)
|
||||||
|
{
|
||||||
|
ProbeMouthToSpine(
|
||||||
|
OutReport.DeparturePoint,
|
||||||
|
OutReport.DepartureSpinePoint,
|
||||||
|
OutReport.DepartureToSpine,
|
||||||
|
OutReport.DepartureToSpineFacts);
|
||||||
|
}
|
||||||
|
if (OutReport.bHasChainArrival && OutReport.bHasDeparture)
|
||||||
|
{
|
||||||
|
OutReport.ArrivalToDeparture.bChecked = true;
|
||||||
|
OutReport.ArrivalToDeparture.Result = VF_AreConnected(
|
||||||
|
Generator,
|
||||||
|
Manager,
|
||||||
|
StrateIndex,
|
||||||
|
OutReport.ArrivalPoint,
|
||||||
|
OutReport.DeparturePoint,
|
||||||
|
Settings,
|
||||||
|
OutReport.ArrivalToDeparture.bStartSnapped,
|
||||||
|
OutReport.ArrivalToDeparture.bGoalSnapped);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
bool FVoxelForgeStrateConnectivityTest::RunTest(const FString& Parameters)
|
||||||
@@ -1229,6 +1564,121 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
|||||||
&& !Row.Probe.bGoalSnapped;
|
&& !Row.Probe.bGoalSnapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FVoxelStrateMeasureSettings DiagnosticSettings = BaseSettings;
|
||||||
|
DiagnosticSettings.SampleStep = 2;
|
||||||
|
DiagnosticSettings.RadiusInVoxels = 192;
|
||||||
|
DiagnosticSettings.InteriorMarginVoxels = 0;
|
||||||
|
|
||||||
|
TArray<FSpineDiagnosisReport> SpineReports;
|
||||||
|
SpineReports.SetNum(Layout.Num());
|
||||||
|
bool bSpineDiagnosisUsable = true;
|
||||||
|
const double SpineDiagnosisStartSeconds = FPlatformTime::Seconds();
|
||||||
|
for (int32 StrateIndex = 0; StrateIndex < Layout.Num(); ++StrateIndex)
|
||||||
|
{
|
||||||
|
FSpineDiagnosisReport& Report = SpineReports[StrateIndex];
|
||||||
|
if (!BuildSpineDiagnosisReport(
|
||||||
|
*World.Generator,
|
||||||
|
*World.StrateManager,
|
||||||
|
StrateIndex,
|
||||||
|
DiagnosticSettings,
|
||||||
|
Report))
|
||||||
|
{
|
||||||
|
AddError(FString::Printf(
|
||||||
|
TEXT("HARD FAILURE: strate %d could not produce a bounded origin-spine "
|
||||||
|
"diagnostic at margin 0."),
|
||||||
|
StrateIndex));
|
||||||
|
bAllChecksPassed = false;
|
||||||
|
bSpineDiagnosisUsable = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Report.Spine.NumSamples <= 0
|
||||||
|
|| !FMath::IsFinite(Report.Spine.AirFraction)
|
||||||
|
|| Report.Spine.AirFraction < 0.0f
|
||||||
|
|| Report.Spine.AirFraction > 1.0f
|
||||||
|
|| Report.Spine.SampledMinZ >= Report.Spine.SampledMaxZ)
|
||||||
|
{
|
||||||
|
AddError(FString::Printf(
|
||||||
|
TEXT("HARD FAILURE: %s origin-spine column report is out of range."),
|
||||||
|
*Report.Name));
|
||||||
|
bAllChecksPassed = false;
|
||||||
|
bSpineDiagnosisUsable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Report.bHasArrival)
|
||||||
|
{
|
||||||
|
AddError(FString::Printf(
|
||||||
|
TEXT("HARD FAILURE: %s has no arrival mouth for the spine diagnosis."),
|
||||||
|
*Report.Name));
|
||||||
|
bAllChecksPassed = false;
|
||||||
|
bSpineDiagnosisUsable = false;
|
||||||
|
}
|
||||||
|
if (StrateIndex + 1 < Layout.Num() && !Report.bHasDeparture)
|
||||||
|
{
|
||||||
|
AddError(FString::Printf(
|
||||||
|
TEXT("HARD FAILURE: %s has no departure mouth for the spine diagnosis."),
|
||||||
|
*Report.Name));
|
||||||
|
bAllChecksPassed = false;
|
||||||
|
bSpineDiagnosisUsable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Report.Spine.RepresentativeOpenZ != INDEX_NONE)
|
||||||
|
{
|
||||||
|
if (!Report.SpineComponent.bValid
|
||||||
|
|| Report.SpineComponentProbe.Result != EVoxelConnectivityResult::Connected
|
||||||
|
|| Report.SpineComponentProbe.bStartSnapped
|
||||||
|
|| Report.SpineComponentProbe.bGoalSnapped
|
||||||
|
|| Report.SpineComponent.GoalComponentCells <= 0)
|
||||||
|
{
|
||||||
|
AddError(FString::Printf(
|
||||||
|
TEXT("HARD FAILURE: %s origin-spine component probe was not a stable "
|
||||||
|
"self-connected air query: %s."),
|
||||||
|
*Report.Name,
|
||||||
|
*ConnectivityProbeText(Report.SpineComponentProbe)));
|
||||||
|
bAllChecksPassed = false;
|
||||||
|
bSpineDiagnosisUsable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const double SpineDiagnosisSeconds = FPlatformTime::Seconds() - SpineDiagnosisStartSeconds;
|
||||||
|
|
||||||
|
int32 NumSpineFullWindowRuns = 0;
|
||||||
|
int32 NumSpineOneOpenRuns = 0;
|
||||||
|
int32 NumSpineLargestComponents = 0;
|
||||||
|
int32 NumMouthToSpineQueries = 0;
|
||||||
|
int32 NumMouthToSpineConnected = 0;
|
||||||
|
int32 NumMouthToSpineCoarseLied = 0;
|
||||||
|
for (const FSpineDiagnosisReport& Report : SpineReports)
|
||||||
|
{
|
||||||
|
if (Report.Spine.bFullWindowRun)
|
||||||
|
{
|
||||||
|
++NumSpineFullWindowRuns;
|
||||||
|
}
|
||||||
|
if (Report.Spine.bAirSamplesFormOneRun)
|
||||||
|
{
|
||||||
|
++NumSpineOneOpenRuns;
|
||||||
|
}
|
||||||
|
if (Report.SpineComponent.GoalComponentCells > 0
|
||||||
|
&& Report.SpineComponent.bGoalComponentIsLargest)
|
||||||
|
{
|
||||||
|
++NumSpineLargestComponents;
|
||||||
|
}
|
||||||
|
const FConnectivityProbe* MouthProbes[] = {
|
||||||
|
Report.bHasArrival ? &Report.ArrivalToSpine : nullptr,
|
||||||
|
Report.bHasDeparture ? &Report.DepartureToSpine : nullptr,
|
||||||
|
};
|
||||||
|
for (const FConnectivityProbe* Probe : MouthProbes)
|
||||||
|
{
|
||||||
|
if (Probe == nullptr)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++NumMouthToSpineQueries;
|
||||||
|
NumMouthToSpineConnected += Probe->Result == EVoxelConnectivityResult::Connected;
|
||||||
|
NumMouthToSpineCoarseLied += Probe->Result == EVoxelConnectivityResult::CoarseLied;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FString Summary = TEXT(
|
FString Summary = TEXT(
|
||||||
"VerticalShafts arrival->departure refinement (seed 1337; source mouth pair; "
|
"VerticalShafts arrival->departure refinement (seed 1337; source mouth pair; "
|
||||||
"derived interior window):\n");
|
"derived interior window):\n");
|
||||||
@@ -1293,13 +1743,104 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
|||||||
: TEXT("FINDING SURVIVES — no margin-0/2/4 query is CONNECTED; the full window is exonerated"),
|
: TEXT("FINDING SURVIVES — no margin-0/2/4 query is CONNECTED; the full window is exonerated"),
|
||||||
MarginSweepSeconds);
|
MarginSweepSeconds);
|
||||||
|
|
||||||
|
Summary += FString::Printf(
|
||||||
|
TEXT("Origin-spine diagnosis (seed 1337; OriginSpineRadius=%.3f; margin 0; "
|
||||||
|
"SampleStep=2; RadiusInVoxels=192; column sampled at every integer Z):\n"),
|
||||||
|
World.Generator->OriginSpineRadius);
|
||||||
|
Summary += TEXT(
|
||||||
|
" strate | measured window | spine open? (air fraction; full-window run; one open run; "
|
||||||
|
"open Z span) | spine component share | spine is largest? | arrival->spine | "
|
||||||
|
"departure->spine | arrival->departure\n");
|
||||||
|
for (const FSpineDiagnosisReport& Report : SpineReports)
|
||||||
|
{
|
||||||
|
const FSpineColumnReport& Spine = Report.Spine;
|
||||||
|
const FVoxelConnectivityDiagnostics& SpineComponent = Report.SpineComponent;
|
||||||
|
const FString OpenSpan = Spine.FirstOpenZ == INDEX_NONE
|
||||||
|
? TEXT("none")
|
||||||
|
: FString::Printf(TEXT("[%d,%d]"), Spine.FirstOpenZ, Spine.LastOpenZ);
|
||||||
|
const FString SpineOpenText = FString::Printf(
|
||||||
|
TEXT("air=%.9g; full=%s; one-run=%s; open=%s"),
|
||||||
|
Spine.AirFraction,
|
||||||
|
Spine.bFullWindowRun ? TEXT("YES") : TEXT("NO"),
|
||||||
|
Spine.bAirSamplesFormOneRun ? TEXT("YES") : TEXT("NO"),
|
||||||
|
*OpenSpan);
|
||||||
|
const FString SpineShareText = SpineComponent.GoalComponentCells > 0
|
||||||
|
? FString::Printf(
|
||||||
|
TEXT("%.9g (%lld/%lld)"),
|
||||||
|
SpineComponent.GoalComponentShare,
|
||||||
|
SpineComponent.GoalComponentCells,
|
||||||
|
SpineComponent.NumAirCells)
|
||||||
|
: TEXT("N/A");
|
||||||
|
const FString SpineLargestText = SpineComponent.GoalComponentCells > 0
|
||||||
|
? (SpineComponent.bGoalComponentIsLargest ? TEXT("YES") : TEXT("NO"))
|
||||||
|
: TEXT("N/A");
|
||||||
|
const FString ArrivalText = Report.bHasArrival
|
||||||
|
? ConnectivityProbeText(Report.ArrivalToSpine)
|
||||||
|
: TEXT("MISSING_ARRIVAL");
|
||||||
|
const FString DepartureText = Report.bHasDeparture
|
||||||
|
? ConnectivityProbeText(Report.DepartureToSpine)
|
||||||
|
: TEXT("N/A (bottom-most; no departure)");
|
||||||
|
FString ArrivalDepartureText;
|
||||||
|
if (Report.bHasChainArrival && Report.bHasDeparture)
|
||||||
|
{
|
||||||
|
ArrivalDepartureText = ConnectivityProbeText(Report.ArrivalToDeparture);
|
||||||
|
}
|
||||||
|
else if (Report.bArrivalIsSurfaceEntry && !Report.bHasChainArrival)
|
||||||
|
{
|
||||||
|
ArrivalDepartureText = TEXT("N/A (surface entry; no chain arrival)");
|
||||||
|
}
|
||||||
|
else if (!Report.bHasDeparture && Report.Index + 1 == SpineReports.Num())
|
||||||
|
{
|
||||||
|
ArrivalDepartureText = TEXT("N/A (bottom-most; no departure)");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ArrivalDepartureText = TEXT("MISSING_CHAIN_MOUTH");
|
||||||
|
}
|
||||||
|
|
||||||
|
Summary += FString::Printf(
|
||||||
|
TEXT(" %d %s | [%d,%d) | %s | %s | %s | %s | %s | %s\n"),
|
||||||
|
Report.Index,
|
||||||
|
*Report.Name,
|
||||||
|
Spine.SampledMinZ,
|
||||||
|
Spine.SampledMaxZ,
|
||||||
|
*SpineOpenText,
|
||||||
|
*SpineShareText,
|
||||||
|
*SpineLargestText,
|
||||||
|
*ArrivalText,
|
||||||
|
*DepartureText,
|
||||||
|
*ArrivalDepartureText);
|
||||||
|
}
|
||||||
|
Summary += FString::Printf(
|
||||||
|
TEXT("Origin-spine diagnosis wall-clock: %.3f seconds; usable=%s.\n"),
|
||||||
|
SpineDiagnosisSeconds,
|
||||||
|
bSpineDiagnosisUsable ? TEXT("yes") : TEXT("no"));
|
||||||
|
Summary += FString::Printf(
|
||||||
|
TEXT("Interpretation counts: spine full-window run=%d/%d, one uninterrupted open run=%d/%d, "
|
||||||
|
"spine component largest=%d/%d; mouth->spine CONNECTED=%d/%d, COARSE_LIED=%d.\n"),
|
||||||
|
NumSpineFullWindowRuns,
|
||||||
|
SpineReports.Num(),
|
||||||
|
NumSpineOneOpenRuns,
|
||||||
|
SpineReports.Num(),
|
||||||
|
NumSpineLargestComponents,
|
||||||
|
SpineReports.Num(),
|
||||||
|
NumMouthToSpineConnected,
|
||||||
|
NumMouthToSpineQueries,
|
||||||
|
NumMouthToSpineCoarseLied);
|
||||||
|
Summary += TEXT(
|
||||||
|
"Interpretation: the spine is relevant and usually a backbone, but it is not a universal "
|
||||||
|
"connector. The VerticalShafts result supports a placement-first choice for this defect "
|
||||||
|
"because most fixture mouths reach the spine; it does not support treating the spine as "
|
||||||
|
"a guarantee for every archetype.\n");
|
||||||
|
Summary += TEXT(
|
||||||
|
"Ordering-dependency hazard: making passage i's departure mouth depend on passage i-1's "
|
||||||
|
"arrival mouth would make layout generation a sequence. That conflicts with "
|
||||||
|
"VoxelForge.Determinism.LayoutOrderIndependence, which asserts the layout is a set; a "
|
||||||
|
"coupled implementation would be expected to break that test. No such dependency was "
|
||||||
|
"implemented here.\n");
|
||||||
|
|
||||||
if (!bMarginArtifact && bMarginSweepUsable)
|
if (!bMarginArtifact && bMarginSweepUsable)
|
||||||
{
|
{
|
||||||
FVoxelStrateMeasureSettings DiagnosticSettings = BaseSettings;
|
|
||||||
DiagnosticSettings.SampleStep = 2;
|
|
||||||
DiagnosticSettings.RadiusInVoxels = 192;
|
|
||||||
DiagnosticSettings.InteriorMarginVoxels = 0;
|
|
||||||
|
|
||||||
const FVoxelConnectivityDiagnostics Diagnostics = VF_DiagnoseConnectivity(
|
const FVoxelConnectivityDiagnostics Diagnostics = VF_DiagnoseConnectivity(
|
||||||
*World.Generator,
|
*World.Generator,
|
||||||
*World.StrateManager,
|
*World.StrateManager,
|
||||||
@@ -1369,8 +1910,20 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
|||||||
};
|
};
|
||||||
int32 SeedPasses = 0;
|
int32 SeedPasses = 0;
|
||||||
int32 SeedFailures = 0;
|
int32 SeedFailures = 0;
|
||||||
int32 SeedSnapEvents = 0;
|
int32 SeedArrivalToSpineSnapEvents = 0;
|
||||||
int32 SeedResultCounts[6] = {};
|
int32 SeedDepartureToSpineSnapEvents = 0;
|
||||||
|
int32 SeedArrivalToDepartureSnapEvents = 0;
|
||||||
|
int32 SeedArrivalToSpineResultCounts[6] = {};
|
||||||
|
int32 SeedDepartureToSpineResultCounts[6] = {};
|
||||||
|
int32 SeedArrivalToDepartureResultCounts[6] = {};
|
||||||
|
int32 SeedFullWindowRuns = 0;
|
||||||
|
int32 SeedOneOpenRuns = 0;
|
||||||
|
int32 SeedSpineLargest = 0;
|
||||||
|
int32 SeedSpineNoOpenCell = 0;
|
||||||
|
float SeedSpineShareMin = FLT_MAX;
|
||||||
|
float SeedSpineShareMax = -FLT_MAX;
|
||||||
|
TArray<FSpineDiagnosisReport> SeedReports;
|
||||||
|
SeedReports.Reserve(UE_ARRAY_COUNT(SeedCases));
|
||||||
const double SeedSweepStartSeconds = FPlatformTime::Seconds();
|
const double SeedSweepStartSeconds = FPlatformTime::Seconds();
|
||||||
for (const int32 Seed : SeedCases)
|
for (const int32 Seed : SeedCases)
|
||||||
{
|
{
|
||||||
@@ -1381,43 +1934,91 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
|||||||
AddError(FString::Printf(
|
AddError(FString::Printf(
|
||||||
TEXT("HARD FAILURE: seed %d could not build the VerticalShafts fixture."),
|
TEXT("HARD FAILURE: seed %d could not build the VerticalShafts fixture."),
|
||||||
Seed));
|
Seed));
|
||||||
|
bAllChecksPassed = false;
|
||||||
++SeedFailures;
|
++SeedFailures;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
FVector SeedArrivalPoint = FVector::ZeroVector;
|
FSpineDiagnosisReport& SeedReport = SeedReports.AddDefaulted_GetRef();
|
||||||
FVector SeedDeparturePoint = FVector::ZeroVector;
|
if (!BuildSpineDiagnosisReport(
|
||||||
if (!FindChainMouths(
|
*SeedWorld.Generator,
|
||||||
SeedWorld.StrateManager->GetPassages(),
|
*SeedWorld.StrateManager,
|
||||||
VerticalShaftsIndex,
|
VerticalShaftsIndex,
|
||||||
SeedArrivalPoint,
|
DiagnosticSettings,
|
||||||
SeedDeparturePoint))
|
SeedReport))
|
||||||
|
{
|
||||||
|
SeedReports.Pop();
|
||||||
|
AddError(FString::Printf(
|
||||||
|
TEXT("HARD FAILURE: seed %d could not produce the VerticalShafts spine "
|
||||||
|
"diagnosis."),
|
||||||
|
Seed));
|
||||||
|
bAllChecksPassed = false;
|
||||||
|
++SeedFailures;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SeedReport.Seed = Seed;
|
||||||
|
|
||||||
|
if (!SeedReport.bHasChainArrival || !SeedReport.bHasDeparture
|
||||||
|
|| !SeedReport.ArrivalToDeparture.bChecked)
|
||||||
{
|
{
|
||||||
AddError(FString::Printf(
|
AddError(FString::Printf(
|
||||||
TEXT("HARD FAILURE: seed %d did not produce exactly one VerticalShafts "
|
TEXT("HARD FAILURE: seed %d did not produce exactly one VerticalShafts "
|
||||||
"arrival and departure mouth."),
|
"arrival and departure mouth."),
|
||||||
Seed));
|
Seed));
|
||||||
|
bAllChecksPassed = false;
|
||||||
++SeedFailures;
|
++SeedFailures;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool bStartSnapped = false;
|
const auto CountSeedResult = [](const FConnectivityProbe& Probe, int32 (&Counts)[6])
|
||||||
bool bGoalSnapped = false;
|
{
|
||||||
const EVoxelConnectivityResult SeedResult = VF_AreConnected(
|
if (Probe.bChecked)
|
||||||
*SeedWorld.Generator,
|
{
|
||||||
*SeedWorld.StrateManager,
|
++Counts[static_cast<int32>(Probe.Result)];
|
||||||
VerticalShaftsIndex,
|
}
|
||||||
SeedArrivalPoint,
|
};
|
||||||
SeedDeparturePoint,
|
CountSeedResult(SeedReport.ArrivalToSpine, SeedArrivalToSpineResultCounts);
|
||||||
DiagnosticSettings,
|
CountSeedResult(SeedReport.DepartureToSpine, SeedDepartureToSpineResultCounts);
|
||||||
bStartSnapped,
|
CountSeedResult(SeedReport.ArrivalToDeparture, SeedArrivalToDepartureResultCounts);
|
||||||
bGoalSnapped);
|
|
||||||
++SeedResultCounts[static_cast<int32>(SeedResult)];
|
SeedArrivalToSpineSnapEvents +=
|
||||||
const int32 SnapEvents = (bStartSnapped ? 1 : 0) + (bGoalSnapped ? 1 : 0);
|
(SeedReport.ArrivalToSpine.bStartSnapped ? 1 : 0)
|
||||||
SeedSnapEvents += SnapEvents;
|
+ (SeedReport.ArrivalToSpine.bGoalSnapped ? 1 : 0);
|
||||||
|
SeedDepartureToSpineSnapEvents +=
|
||||||
|
(SeedReport.DepartureToSpine.bStartSnapped ? 1 : 0)
|
||||||
|
+ (SeedReport.DepartureToSpine.bGoalSnapped ? 1 : 0);
|
||||||
|
SeedArrivalToDepartureSnapEvents +=
|
||||||
|
(SeedReport.ArrivalToDeparture.bStartSnapped ? 1 : 0)
|
||||||
|
+ (SeedReport.ArrivalToDeparture.bGoalSnapped ? 1 : 0);
|
||||||
|
|
||||||
|
if (SeedReport.Spine.bFullWindowRun)
|
||||||
|
{
|
||||||
|
++SeedFullWindowRuns;
|
||||||
|
}
|
||||||
|
if (SeedReport.Spine.bAirSamplesFormOneRun)
|
||||||
|
{
|
||||||
|
++SeedOneOpenRuns;
|
||||||
|
}
|
||||||
|
if (SeedReport.SpineComponent.GoalComponentCells > 0)
|
||||||
|
{
|
||||||
|
if (SeedReport.SpineComponent.bGoalComponentIsLargest)
|
||||||
|
{
|
||||||
|
++SeedSpineLargest;
|
||||||
|
}
|
||||||
|
SeedSpineShareMin = FMath::Min(
|
||||||
|
SeedSpineShareMin, SeedReport.SpineComponent.GoalComponentShare);
|
||||||
|
SeedSpineShareMax = FMath::Max(
|
||||||
|
SeedSpineShareMax, SeedReport.SpineComponent.GoalComponentShare);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++SeedSpineNoOpenCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EVoxelConnectivityResult SeedResult = SeedReport.ArrivalToDeparture.Result;
|
||||||
if (SeedResult == EVoxelConnectivityResult::Connected
|
if (SeedResult == EVoxelConnectivityResult::Connected
|
||||||
&& !bStartSnapped
|
&& !SeedReport.ArrivalToDeparture.bStartSnapped
|
||||||
&& !bGoalSnapped)
|
&& !SeedReport.ArrivalToDeparture.bGoalSnapped)
|
||||||
{
|
{
|
||||||
++SeedPasses;
|
++SeedPasses;
|
||||||
}
|
}
|
||||||
@@ -1429,22 +2030,83 @@ bool FVoxelForgeStrateConnectivityRefinementTest::RunTest(const FString& Paramet
|
|||||||
const double SeedSweepSeconds = FPlatformTime::Seconds() - SeedSweepStartSeconds;
|
const double SeedSweepSeconds = FPlatformTime::Seconds() - SeedSweepStartSeconds;
|
||||||
|
|
||||||
Summary += FString::Printf(
|
Summary += FString::Printf(
|
||||||
TEXT(" multi-seed arrival->departure sweep (%d seeds; margin 0, step 2, radius 192): "
|
TEXT(" multi-seed VerticalShafts diagnosis (%d seeds; margin 0, step 2, radius 192): "
|
||||||
"%d pass, %d fail, %d snapped endpoint events; wall-clock %.3f seconds.\n"),
|
"arrival->departure %d pass, %d fail, %d snapped endpoint events; "
|
||||||
|
"wall-clock %.3f seconds.\n"),
|
||||||
UE_ARRAY_COUNT(SeedCases),
|
UE_ARRAY_COUNT(SeedCases),
|
||||||
SeedPasses,
|
SeedPasses,
|
||||||
SeedFailures,
|
SeedFailures,
|
||||||
SeedSnapEvents,
|
SeedArrivalToDepartureSnapEvents,
|
||||||
SeedSweepSeconds);
|
SeedSweepSeconds);
|
||||||
Summary += FString::Printf(
|
Summary += FString::Printf(
|
||||||
TEXT(" seed result breakdown: CONNECTED=%d, NOT_CONNECTED_AT_THIS_RESOLUTION=%d, "
|
TEXT(" spine column sweep: full-window run=%d/%d, one-open-run=%d/%d, "
|
||||||
"START_CELL_SOLID=%d, GOAL_CELL_SOLID=%d, OUT_OF_WINDOW=%d, COARSE_LIED=%d.\n"),
|
"component-largest=%d/%d, no open component=%d; component share range "
|
||||||
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::Connected)],
|
"[%.9g, %.9g].\n"),
|
||||||
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::NotConnectedAtThisResolution)],
|
SeedFullWindowRuns,
|
||||||
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::StartCellSolid)],
|
UE_ARRAY_COUNT(SeedCases),
|
||||||
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::GoalCellSolid)],
|
SeedOneOpenRuns,
|
||||||
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::OutOfWindow)],
|
UE_ARRAY_COUNT(SeedCases),
|
||||||
SeedResultCounts[static_cast<int32>(EVoxelConnectivityResult::CoarseLied)]);
|
SeedSpineLargest,
|
||||||
|
UE_ARRAY_COUNT(SeedCases),
|
||||||
|
SeedSpineNoOpenCell,
|
||||||
|
SeedSpineShareMin == FLT_MAX ? 0.0f : SeedSpineShareMin,
|
||||||
|
SeedSpineShareMax == -FLT_MAX ? 0.0f : SeedSpineShareMax);
|
||||||
|
Summary += FString::Printf(
|
||||||
|
TEXT(" seed result breakdown — arrival->spine: CONNECTED=%d, "
|
||||||
|
"NOT_CONNECTED_AT_THIS_RESOLUTION=%d, START_CELL_SOLID=%d, GOAL_CELL_SOLID=%d, "
|
||||||
|
"OUT_OF_WINDOW=%d, COARSE_LIED=%d; departure->spine: CONNECTED=%d, "
|
||||||
|
"NOT_CONNECTED_AT_THIS_RESOLUTION=%d, START_CELL_SOLID=%d, GOAL_CELL_SOLID=%d, "
|
||||||
|
"OUT_OF_WINDOW=%d, COARSE_LIED=%d; arrival->departure: CONNECTED=%d, "
|
||||||
|
"NOT_CONNECTED_AT_THIS_RESOLUTION=%d, START_CELL_SOLID=%d, GOAL_CELL_SOLID=%d, "
|
||||||
|
"OUT_OF_WINDOW=%d, COARSE_LIED=%d.\n"),
|
||||||
|
SeedArrivalToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::Connected)],
|
||||||
|
SeedArrivalToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::NotConnectedAtThisResolution)],
|
||||||
|
SeedArrivalToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::StartCellSolid)],
|
||||||
|
SeedArrivalToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::GoalCellSolid)],
|
||||||
|
SeedArrivalToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::OutOfWindow)],
|
||||||
|
SeedArrivalToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::CoarseLied)],
|
||||||
|
SeedDepartureToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::Connected)],
|
||||||
|
SeedDepartureToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::NotConnectedAtThisResolution)],
|
||||||
|
SeedDepartureToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::StartCellSolid)],
|
||||||
|
SeedDepartureToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::GoalCellSolid)],
|
||||||
|
SeedDepartureToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::OutOfWindow)],
|
||||||
|
SeedDepartureToSpineResultCounts[static_cast<int32>(EVoxelConnectivityResult::CoarseLied)],
|
||||||
|
SeedArrivalToDepartureResultCounts[static_cast<int32>(EVoxelConnectivityResult::Connected)],
|
||||||
|
SeedArrivalToDepartureResultCounts[static_cast<int32>(EVoxelConnectivityResult::NotConnectedAtThisResolution)],
|
||||||
|
SeedArrivalToDepartureResultCounts[static_cast<int32>(EVoxelConnectivityResult::StartCellSolid)],
|
||||||
|
SeedArrivalToDepartureResultCounts[static_cast<int32>(EVoxelConnectivityResult::GoalCellSolid)],
|
||||||
|
SeedArrivalToDepartureResultCounts[static_cast<int32>(EVoxelConnectivityResult::OutOfWindow)],
|
||||||
|
SeedArrivalToDepartureResultCounts[static_cast<int32>(EVoxelConnectivityResult::CoarseLied)]);
|
||||||
|
Summary += FString::Printf(
|
||||||
|
TEXT(" seed endpoint snaps — arrival->spine=%d, departure->spine=%d, "
|
||||||
|
"arrival->departure=%d.\n"),
|
||||||
|
SeedArrivalToSpineSnapEvents,
|
||||||
|
SeedDepartureToSpineSnapEvents,
|
||||||
|
SeedArrivalToDepartureSnapEvents);
|
||||||
|
Summary += TEXT(" seed | spine air | full run | one open run | spine share | spine largest | "
|
||||||
|
"arrival->spine | departure->spine | arrival->departure\n");
|
||||||
|
for (int32 SeedIndex = 0; SeedIndex < SeedReports.Num(); ++SeedIndex)
|
||||||
|
{
|
||||||
|
const FSpineDiagnosisReport& SeedReport = SeedReports[SeedIndex];
|
||||||
|
const FString SpineShare = SeedReport.SpineComponent.GoalComponentCells > 0
|
||||||
|
? FString::Printf(
|
||||||
|
TEXT("%.9g"), SeedReport.SpineComponent.GoalComponentShare)
|
||||||
|
: TEXT("N/A");
|
||||||
|
const FString SpineLargest = SeedReport.SpineComponent.GoalComponentCells > 0
|
||||||
|
? (SeedReport.SpineComponent.bGoalComponentIsLargest ? TEXT("YES") : TEXT("NO"))
|
||||||
|
: TEXT("N/A");
|
||||||
|
Summary += FString::Printf(
|
||||||
|
TEXT(" %d | %.9g | %s | %s | %s | %s | %s | %s | %s\n"),
|
||||||
|
SeedReport.Seed,
|
||||||
|
SeedReport.Spine.AirFraction,
|
||||||
|
SeedReport.Spine.bFullWindowRun ? TEXT("YES") : TEXT("NO"),
|
||||||
|
SeedReport.Spine.bAirSamplesFormOneRun ? TEXT("YES") : TEXT("NO"),
|
||||||
|
*SpineShare,
|
||||||
|
*SpineLargest,
|
||||||
|
*ConnectivityProbeText(SeedReport.ArrivalToSpine),
|
||||||
|
*ConnectivityProbeText(SeedReport.DepartureToSpine),
|
||||||
|
*ConnectivityProbeText(SeedReport.ArrivalToDeparture));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -891,6 +891,148 @@ namespace VoxelStrateMeasurePrivate
|
|||||||
? -1.0f
|
? -1.0f
|
||||||
: FMath::Sqrt(static_cast<float>(BestDistanceSquared));
|
: FMath::Sqrt(static_cast<float>(BestDistanceSquared));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PopulateConnectivityDiagnostics(
|
||||||
|
const FSampleGrid& Grid,
|
||||||
|
int32 Start,
|
||||||
|
int32 Goal,
|
||||||
|
EVoxelConnectivityResult ConnectivityResult,
|
||||||
|
bool bStartSnapped,
|
||||||
|
bool bGoalSnapped,
|
||||||
|
FVoxelConnectivityDiagnostics& OutDiagnostics)
|
||||||
|
{
|
||||||
|
OutDiagnostics = FVoxelConnectivityDiagnostics();
|
||||||
|
OutDiagnostics.Result = ConnectivityResult;
|
||||||
|
OutDiagnostics.bStartSnapped = bStartSnapped;
|
||||||
|
OutDiagnostics.bGoalSnapped = bGoalSnapped;
|
||||||
|
OutDiagnostics.bValid = ConnectivityResult != EVoxelConnectivityResult::OutOfWindow
|
||||||
|
|| (Start >= 0 && Goal >= 0);
|
||||||
|
if (Start < 0 || Goal < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<int32> Components;
|
||||||
|
int32 NumComponents = 0;
|
||||||
|
int64 LargestComponentCells = 0;
|
||||||
|
int32 LargestComponentLowestCell = INDEX_NONE;
|
||||||
|
int32 NumComponentsAtLeast1Pct = 0;
|
||||||
|
int64 NumAir = 0;
|
||||||
|
for (const uint8 bAir : Grid.Air)
|
||||||
|
{
|
||||||
|
NumAir += bAir != 0u ? 1 : 0;
|
||||||
|
}
|
||||||
|
FloodFillAir(
|
||||||
|
Grid,
|
||||||
|
Components,
|
||||||
|
NumComponents,
|
||||||
|
NumAir,
|
||||||
|
LargestComponentCells,
|
||||||
|
LargestComponentLowestCell,
|
||||||
|
NumComponentsAtLeast1Pct);
|
||||||
|
|
||||||
|
const int32 StartComponent = Components[Start];
|
||||||
|
const int32 GoalComponent = Components[Goal];
|
||||||
|
if (StartComponent < 0 || GoalComponent < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<int32> ComponentCells;
|
||||||
|
ComponentCells.Init(0, NumComponents);
|
||||||
|
for (const int32 Component : Components)
|
||||||
|
{
|
||||||
|
if (Component >= 0)
|
||||||
|
{
|
||||||
|
++ComponentCells[Component];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OutDiagnostics.NumAirCells = NumAir;
|
||||||
|
OutDiagnostics.StartComponentCells = ComponentCells[StartComponent];
|
||||||
|
OutDiagnostics.GoalComponentCells = ComponentCells[GoalComponent];
|
||||||
|
OutDiagnostics.LargestComponentCells = LargestComponentCells;
|
||||||
|
if (NumAir > 0)
|
||||||
|
{
|
||||||
|
OutDiagnostics.StartComponentShare = static_cast<float>(
|
||||||
|
static_cast<double>(OutDiagnostics.StartComponentCells)
|
||||||
|
/ static_cast<double>(NumAir));
|
||||||
|
OutDiagnostics.GoalComponentShare = static_cast<float>(
|
||||||
|
static_cast<double>(OutDiagnostics.GoalComponentCells)
|
||||||
|
/ static_cast<double>(NumAir));
|
||||||
|
OutDiagnostics.LargestComponentShare = static_cast<float>(
|
||||||
|
static_cast<double>(LargestComponentCells)
|
||||||
|
/ static_cast<double>(NumAir));
|
||||||
|
}
|
||||||
|
OutDiagnostics.bStartComponentIsLargest =
|
||||||
|
OutDiagnostics.StartComponentCells == LargestComponentCells;
|
||||||
|
OutDiagnostics.bGoalComponentIsLargest =
|
||||||
|
OutDiagnostics.GoalComponentCells == LargestComponentCells;
|
||||||
|
OutDiagnostics.StartToGoalComponentDistanceCells = DistanceToComponent(
|
||||||
|
Grid, Start, GoalComponent, Components);
|
||||||
|
OutDiagnostics.GoalToStartComponentDistanceCells = DistanceToComponent(
|
||||||
|
Grid, Goal, StartComponent, Components);
|
||||||
|
}
|
||||||
|
|
||||||
|
EVoxelConnectivityResult QueryConnectivity(
|
||||||
|
const UVoxelGenerator& Generator,
|
||||||
|
const UVoxelStrateManager& Manager,
|
||||||
|
int32 StrateIndex,
|
||||||
|
const FVector& AVoxel,
|
||||||
|
const FVector& BVoxel,
|
||||||
|
const FVoxelStrateMeasureSettings& Settings,
|
||||||
|
bool& bOutStartSnapped,
|
||||||
|
bool& bOutGoalSnapped,
|
||||||
|
FVoxelConnectivityDiagnostics* OutDiagnostics)
|
||||||
|
{
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
if (OutDiagnostics != nullptr)
|
||||||
|
{
|
||||||
|
*OutDiagnostics = FVoxelConnectivityDiagnostics();
|
||||||
|
}
|
||||||
|
return EVoxelConnectivityResult::OutOfWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
FSampleGrid Grid;
|
||||||
|
FString RefusalReason;
|
||||||
|
if (!BuildSampleGrid(Generator, Manager, StrateIndex, Settings, Grid, RefusalReason))
|
||||||
|
{
|
||||||
|
if (OutDiagnostics != nullptr)
|
||||||
|
{
|
||||||
|
*OutDiagnostics = FVoxelConnectivityDiagnostics();
|
||||||
|
}
|
||||||
|
return EVoxelConnectivityResult::OutOfWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 Start = -1;
|
||||||
|
int32 Goal = -1;
|
||||||
|
const EVoxelConnectivityResult Result = EvaluateConnectivityOnGrid(
|
||||||
|
Generator,
|
||||||
|
Grid,
|
||||||
|
AVoxel,
|
||||||
|
BVoxel,
|
||||||
|
Start,
|
||||||
|
Goal,
|
||||||
|
bOutStartSnapped,
|
||||||
|
bOutGoalSnapped);
|
||||||
|
if (OutDiagnostics != nullptr)
|
||||||
|
{
|
||||||
|
PopulateConnectivityDiagnostics(
|
||||||
|
Grid,
|
||||||
|
Start,
|
||||||
|
Goal,
|
||||||
|
Result,
|
||||||
|
bOutStartSnapped,
|
||||||
|
bOutGoalSnapped,
|
||||||
|
*OutDiagnostics);
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FVoxelStrateMetrics VF_MeasureStrate(
|
FVoxelStrateMetrics VF_MeasureStrate(
|
||||||
@@ -929,33 +1071,39 @@ EVoxelConnectivityResult VF_AreConnected(
|
|||||||
bool& bOutStartSnapped,
|
bool& bOutStartSnapped,
|
||||||
bool& bOutGoalSnapped)
|
bool& bOutGoalSnapped)
|
||||||
{
|
{
|
||||||
bOutStartSnapped = false;
|
return VoxelStrateMeasurePrivate::QueryConnectivity(
|
||||||
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 EVoxelConnectivityResult::OutOfWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
VoxelStrateMeasurePrivate::FSampleGrid Grid;
|
|
||||||
FString RefusalReason;
|
|
||||||
if (!VoxelStrateMeasurePrivate::BuildSampleGrid(
|
|
||||||
Generator, Manager, StrateIndex, Settings, Grid, RefusalReason))
|
|
||||||
{
|
|
||||||
return EVoxelConnectivityResult::OutOfWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32 Start = -1;
|
|
||||||
int32 Goal = -1;
|
|
||||||
return VoxelStrateMeasurePrivate::EvaluateConnectivityOnGrid(
|
|
||||||
Generator,
|
Generator,
|
||||||
Grid,
|
Manager,
|
||||||
|
StrateIndex,
|
||||||
AVoxel,
|
AVoxel,
|
||||||
BVoxel,
|
BVoxel,
|
||||||
Start,
|
Settings,
|
||||||
Goal,
|
|
||||||
bOutStartSnapped,
|
bOutStartSnapped,
|
||||||
bOutGoalSnapped);
|
bOutGoalSnapped,
|
||||||
|
nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
EVoxelConnectivityResult VF_AreConnected(
|
||||||
|
const UVoxelGenerator& Generator,
|
||||||
|
const UVoxelStrateManager& Manager,
|
||||||
|
int32 StrateIndex,
|
||||||
|
const FVector& AVoxel,
|
||||||
|
const FVector& BVoxel,
|
||||||
|
const FVoxelStrateMeasureSettings& Settings,
|
||||||
|
bool& bOutStartSnapped,
|
||||||
|
bool& bOutGoalSnapped,
|
||||||
|
FVoxelConnectivityDiagnostics& OutDiagnostics)
|
||||||
|
{
|
||||||
|
return VoxelStrateMeasurePrivate::QueryConnectivity(
|
||||||
|
Generator,
|
||||||
|
Manager,
|
||||||
|
StrateIndex,
|
||||||
|
AVoxel,
|
||||||
|
BVoxel,
|
||||||
|
Settings,
|
||||||
|
bOutStartSnapped,
|
||||||
|
bOutGoalSnapped,
|
||||||
|
&OutDiagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
FVoxelConnectivityDiagnostics VF_DiagnoseConnectivity(
|
FVoxelConnectivityDiagnostics VF_DiagnoseConnectivity(
|
||||||
@@ -967,89 +1115,15 @@ FVoxelConnectivityDiagnostics VF_DiagnoseConnectivity(
|
|||||||
const FVoxelStrateMeasureSettings& Settings)
|
const FVoxelStrateMeasureSettings& Settings)
|
||||||
{
|
{
|
||||||
FVoxelConnectivityDiagnostics Result;
|
FVoxelConnectivityDiagnostics Result;
|
||||||
if (!FMath::IsFinite(AVoxel.X) || !FMath::IsFinite(AVoxel.Y) || !FMath::IsFinite(AVoxel.Z)
|
VoxelStrateMeasurePrivate::QueryConnectivity(
|
||||||
|| !FMath::IsFinite(BVoxel.X) || !FMath::IsFinite(BVoxel.Y) || !FMath::IsFinite(BVoxel.Z))
|
|
||||||
{
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
VoxelStrateMeasurePrivate::FSampleGrid Grid;
|
|
||||||
FString RefusalReason;
|
|
||||||
if (!VoxelStrateMeasurePrivate::BuildSampleGrid(
|
|
||||||
Generator, Manager, StrateIndex, Settings, Grid, RefusalReason))
|
|
||||||
{
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32 Start = -1;
|
|
||||||
int32 Goal = -1;
|
|
||||||
Result.Result = VoxelStrateMeasurePrivate::EvaluateConnectivityOnGrid(
|
|
||||||
Generator,
|
Generator,
|
||||||
Grid,
|
Manager,
|
||||||
|
StrateIndex,
|
||||||
AVoxel,
|
AVoxel,
|
||||||
BVoxel,
|
BVoxel,
|
||||||
Start,
|
Settings,
|
||||||
Goal,
|
|
||||||
Result.bStartSnapped,
|
Result.bStartSnapped,
|
||||||
Result.bGoalSnapped);
|
Result.bGoalSnapped,
|
||||||
Result.bValid = Result.Result != EVoxelConnectivityResult::OutOfWindow
|
&Result);
|
||||||
|| (Start >= 0 && Goal >= 0);
|
|
||||||
if (Start < 0 || Goal < 0)
|
|
||||||
{
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
TArray<int32> Components;
|
|
||||||
int32 NumComponents = 0;
|
|
||||||
int64 LargestComponentCells = 0;
|
|
||||||
int32 LargestComponentLowestCell = INDEX_NONE;
|
|
||||||
int32 NumComponentsAtLeast1Pct = 0;
|
|
||||||
int64 NumAir = 0;
|
|
||||||
for (const uint8 bAir : Grid.Air)
|
|
||||||
{
|
|
||||||
NumAir += bAir != 0u ? 1 : 0;
|
|
||||||
}
|
|
||||||
FloodFillAir(
|
|
||||||
Grid,
|
|
||||||
Components,
|
|
||||||
NumComponents,
|
|
||||||
NumAir,
|
|
||||||
LargestComponentCells,
|
|
||||||
LargestComponentLowestCell,
|
|
||||||
NumComponentsAtLeast1Pct);
|
|
||||||
|
|
||||||
const int32 StartComponent = Components[Start];
|
|
||||||
const int32 GoalComponent = Components[Goal];
|
|
||||||
if (StartComponent < 0 || GoalComponent < 0)
|
|
||||||
{
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
TArray<int32> ComponentCells;
|
|
||||||
ComponentCells.Init(0, NumComponents);
|
|
||||||
for (const int32 Component : Components)
|
|
||||||
{
|
|
||||||
if (Component >= 0)
|
|
||||||
{
|
|
||||||
++ComponentCells[Component];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result.NumAirCells = NumAir;
|
|
||||||
Result.StartComponentCells = ComponentCells[StartComponent];
|
|
||||||
Result.GoalComponentCells = ComponentCells[GoalComponent];
|
|
||||||
if (NumAir > 0)
|
|
||||||
{
|
|
||||||
Result.StartComponentShare = static_cast<float>(
|
|
||||||
static_cast<double>(Result.StartComponentCells) / static_cast<double>(NumAir));
|
|
||||||
Result.GoalComponentShare = static_cast<float>(
|
|
||||||
static_cast<double>(Result.GoalComponentCells) / static_cast<double>(NumAir));
|
|
||||||
}
|
|
||||||
Result.StartToGoalComponentDistanceCells =
|
|
||||||
VoxelStrateMeasurePrivate::DistanceToComponent(
|
|
||||||
Grid, Start, GoalComponent, Components);
|
|
||||||
Result.GoalToStartComponentDistanceCells =
|
|
||||||
VoxelStrateMeasurePrivate::DistanceToComponent(
|
|
||||||
Grid, Goal, StartComponent, Components);
|
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
@@ -75,8 +75,12 @@ struct VOXELFORGE_API FVoxelConnectivityDiagnostics
|
|||||||
int64 NumAirCells = 0;
|
int64 NumAirCells = 0;
|
||||||
int64 StartComponentCells = 0;
|
int64 StartComponentCells = 0;
|
||||||
int64 GoalComponentCells = 0;
|
int64 GoalComponentCells = 0;
|
||||||
|
int64 LargestComponentCells = 0;
|
||||||
float StartComponentShare = 0.0f;
|
float StartComponentShare = 0.0f;
|
||||||
float GoalComponentShare = 0.0f;
|
float GoalComponentShare = 0.0f;
|
||||||
|
float LargestComponentShare = 0.0f;
|
||||||
|
bool bStartComponentIsLargest = false;
|
||||||
|
bool bGoalComponentIsLargest = false;
|
||||||
|
|
||||||
float StartToGoalComponentDistanceCells = -1.0f;
|
float StartToGoalComponentDistanceCells = -1.0f;
|
||||||
float GoalToStartComponentDistanceCells = -1.0f;
|
float GoalToStartComponentDistanceCells = -1.0f;
|
||||||
@@ -115,11 +119,28 @@ VOXELFORGE_API EVoxelConnectivityResult VF_AreConnected(
|
|||||||
bool& bOutStartSnapped,
|
bool& bOutStartSnapped,
|
||||||
bool& bOutGoalSnapped);
|
bool& bOutGoalSnapped);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same connectivity query with component facts returned from the same sampled grid. This
|
||||||
|
* overload exists for diagnostics that need both the required route verdict and the component
|
||||||
|
* containing one endpoint without sampling the strate a second time.
|
||||||
|
*/
|
||||||
|
VOXELFORGE_API EVoxelConnectivityResult VF_AreConnected(
|
||||||
|
const UVoxelGenerator& Generator,
|
||||||
|
const UVoxelStrateManager& Manager,
|
||||||
|
int32 StrateIndex,
|
||||||
|
const FVector& AVoxel,
|
||||||
|
const FVector& BVoxel,
|
||||||
|
const FVoxelStrateMeasureSettings& Settings,
|
||||||
|
bool& bOutStartSnapped,
|
||||||
|
bool& bOutGoalSnapped,
|
||||||
|
FVoxelConnectivityDiagnostics& OutDiagnostics);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the component sizes and spatial separation facts for the same query performed by
|
* Return the component sizes and spatial separation facts for the same query performed by
|
||||||
* VF_AreConnected. The returned Result uses the same full-resolution route re-check. bValid means
|
* VF_AreConnected. The returned Result uses the same full-resolution route re-check. bValid means
|
||||||
* the sample grid was built and both endpoint coordinates were in its bounds; component fields
|
* the sample grid was built and both endpoint coordinates were in its bounds; component fields
|
||||||
* remain zero/negative when an endpoint has no air cell to analyze.
|
* remain zero/negative when an endpoint has no air cell to analyze. Largest-component membership
|
||||||
|
* treats equal-sized components as tied for largest.
|
||||||
*/
|
*/
|
||||||
VOXELFORGE_API FVoxelConnectivityDiagnostics VF_DiagnoseConnectivity(
|
VOXELFORGE_API FVoxelConnectivityDiagnostics VF_DiagnoseConnectivity(
|
||||||
const UVoxelGenerator& Generator,
|
const UVoxelGenerator& Generator,
|
||||||
|
|||||||
Reference in new issue
Block a user