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