test(explore): 81 runs in an afternoon -- every failure is a DROP, not a squeeze
First real use of the commandlet, and it paid for itself immediately: 64
sweep runs + failure renders + origin probes, which at the old 73-minutes-
per-automation-run would have been days.
⭐ FINDING 1: THE WORLD IS FRAGMENTED VERTICALLY, NOT PINCHED.
Across all 64 runs: ZERO narrow-gap events. Every failed run has separate
arrival/departure fit components with a positive gap, 12.75 m to 72.98 m.
So corridors are not too tight anywhere -- the space is severed.
And for the first time in this project we LOOKED at a failure instead of
inferring from aggregates:
FlatPlain/42 "a broad flat floor sheet with a sharp diagonal ledge
descending into black open space" -- a drop, gap 63.9 m
SurfaceWorld/7 "terrain shelves and a large black void between them.
There is no connecting floor" -- gap 51.8 m
Maze/42 "a massive solid rock wall/overhang fills the separation"
-- gap 27.7 m
I checked the images myself. FlatPlain shows two floor LEVELS 27 m apart
in Z with nothing joining them.
⇒ The missing thing is VERTICAL CONNECTION between floor levels. Ramps,
slopes, ledges, stairs. Every archetype builds floors; nothing builds a way
between them.
⭐ FINDING 2: THE ORIGIN HUB IS NOT REACHABLE, which contradicts 413c15d.
agent's reachable set included the origin column: 0 / 64 runs
origin column had ANY player-fit cell: 17 / 64 runs
Maze slot 4, all 8 seeds: 0 fit cells, unreachable
Even in the 17, the origin cells were not in the arrival component.
The reason is structural and obvious in hindsight: the origin spine is a
VERTICAL CYLINDER. A vertical shaft has no walkable floor except at its
bottom, so a scalar-density carve that guarantees open air guarantees
nothing standable. The landings join a hub you cannot stand in.
That is a finding about the §6.5 hub join as exposed to the player-fit
layer -- not walk tuning. Recorded rather than patched.
Also from Jahni, looking at the renders -- two things numbers missed:
- The render cameras sit INSIDE ROCK, viewing tunnel surfaces from the
solid side. Cameras must be seeded from the player-fit set.
- ⚠️ THE MAZE IS STILL A LATTICE OF STRAIGHT PATHS. The spanning tree
changed WHICH edges exist, not what an edge LOOKS like: every corridor is
still a straight axis-aligned capsule between cell centres, every junction
90 deg. So 15.9% dead ends and mean degree 2.504 measured maze-ness of the
TOPOLOGY while the eye sees a sparser grid. Sixth time a metric answered a
question adjacent to the one that mattered.
Two tool issues found: PowerShell needs -modes="render,walk" quoted or it
parses as render-only; and "showcase slot" is ambiguous (cards use 1/6, the
Maze diagnosis uses 4).
Determinism spot-checked: repeated arguments give identical SHA-256 JSON.
Measurement-only changes; no generation, parameter or threshold edits.
Editor build succeeded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
This commit is contained in:
1 parent
bd547c35c4
commit
6b916699af
3 files changed
+592
-4
No files matched your search
@@ -213,6 +213,16 @@ namespace VoxelStrateMeasurePrivate
|
|||||||
MinY64 = FMath::Min(MinY64, 0.0);
|
MinY64 = FMath::Min(MinY64, 0.0);
|
||||||
MaxY64 = FMath::Max(MaxY64, 0.0);
|
MaxY64 = FMath::Max(MaxY64, 0.0);
|
||||||
}
|
}
|
||||||
|
if (Settings.bForceOriginColumnInCoverWindow)
|
||||||
|
{
|
||||||
|
// Upper bounds are exclusive. The ordinary origin policy can therefore stop at
|
||||||
|
// exactly X=0/Y=0 when both mouths are on one side; a diagnostic column probe
|
||||||
|
// needs one real sampled column straddling the origin instead.
|
||||||
|
MinX64 = FMath::Min(MinX64, -1.0);
|
||||||
|
MaxX64 = FMath::Max(MaxX64, 1.0);
|
||||||
|
MinY64 = FMath::Min(MinY64, -1.0);
|
||||||
|
MaxY64 = FMath::Max(MaxY64, 1.0);
|
||||||
|
}
|
||||||
WindowCenter = FVector2D(
|
WindowCenter = FVector2D(
|
||||||
static_cast<float>(0.5 * (MinX64 + MaxX64)),
|
static_cast<float>(0.5 * (MinX64 + MaxX64)),
|
||||||
static_cast<float>(0.5 * (MinY64 + MaxY64)));
|
static_cast<float>(0.5 * (MinY64 + MaxY64)));
|
||||||
@@ -2092,9 +2102,19 @@ namespace VoxelStrateMeasurePrivate
|
|||||||
bool bGoalSnapped,
|
bool bGoalSnapped,
|
||||||
int32 NumRouteRetries,
|
int32 NumRouteRetries,
|
||||||
FVoxelConnectivityDiagnostics& OutDiagnostics,
|
FVoxelConnectivityDiagnostics& OutDiagnostics,
|
||||||
const TArray<uint8>* EligibilityMask = nullptr)
|
const TArray<uint8>* EligibilityMask = nullptr,
|
||||||
|
TArray<int32>* OutComponents = nullptr,
|
||||||
|
int32* OutNumComponents = nullptr)
|
||||||
{
|
{
|
||||||
OutDiagnostics = FVoxelConnectivityDiagnostics();
|
OutDiagnostics = FVoxelConnectivityDiagnostics();
|
||||||
|
if (OutComponents != nullptr)
|
||||||
|
{
|
||||||
|
OutComponents->Reset();
|
||||||
|
}
|
||||||
|
if (OutNumComponents != nullptr)
|
||||||
|
{
|
||||||
|
*OutNumComponents = 0;
|
||||||
|
}
|
||||||
OutDiagnostics.Result = ConnectivityResult;
|
OutDiagnostics.Result = ConnectivityResult;
|
||||||
OutDiagnostics.bPlayerFitRestricted = EligibilityMask != nullptr;
|
OutDiagnostics.bPlayerFitRestricted = EligibilityMask != nullptr;
|
||||||
OutDiagnostics.bPlayerFitResolved = EligibilityMask != nullptr;
|
OutDiagnostics.bPlayerFitResolved = EligibilityMask != nullptr;
|
||||||
@@ -2131,6 +2151,15 @@ namespace VoxelStrateMeasurePrivate
|
|||||||
nullptr,
|
nullptr,
|
||||||
EligibilityMask);
|
EligibilityMask);
|
||||||
|
|
||||||
|
if (OutComponents != nullptr)
|
||||||
|
{
|
||||||
|
*OutComponents = Components;
|
||||||
|
}
|
||||||
|
if (OutNumComponents != nullptr)
|
||||||
|
{
|
||||||
|
*OutNumComponents = NumComponents;
|
||||||
|
}
|
||||||
|
|
||||||
const int32 StartComponent = Components[Start];
|
const int32 StartComponent = Components[Start];
|
||||||
const int32 GoalComponent = Components[Goal];
|
const int32 GoalComponent = Components[Goal];
|
||||||
if (StartComponent < 0 || GoalComponent < 0)
|
if (StartComponent < 0 || GoalComponent < 0)
|
||||||
@@ -2435,14 +2464,21 @@ namespace VoxelStrateMeasurePrivate
|
|||||||
int32 Start,
|
int32 Start,
|
||||||
int32 Goal,
|
int32 Goal,
|
||||||
float NarrowGapThresholdVoxels,
|
float NarrowGapThresholdVoxels,
|
||||||
FVoxelPlayerFitWalkReport& InOutReport)
|
FVoxelPlayerFitWalkReport& InOutReport,
|
||||||
|
int32& OutFinalCell,
|
||||||
|
int32& OutLastReachedCell)
|
||||||
{
|
{
|
||||||
|
OutFinalCell = INDEX_NONE;
|
||||||
|
OutLastReachedCell = INDEX_NONE;
|
||||||
if (!IsEligiblePlayerFitCell(Grid, PlayerFitMask, Start)
|
if (!IsEligiblePlayerFitCell(Grid, PlayerFitMask, Start)
|
||||||
|| !IsEligiblePlayerFitCell(Grid, PlayerFitMask, Goal))
|
|| !IsEligiblePlayerFitCell(Grid, PlayerFitMask, Goal))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OutFinalCell = Start;
|
||||||
|
OutLastReachedCell = Start;
|
||||||
|
|
||||||
TArray<uint8> Visited;
|
TArray<uint8> Visited;
|
||||||
Visited.Init(0u, Grid.CellCount);
|
Visited.Init(0u, Grid.CellCount);
|
||||||
TArray<uint8> NarrowCell;
|
TArray<uint8> NarrowCell;
|
||||||
@@ -2480,6 +2516,7 @@ namespace VoxelStrateMeasurePrivate
|
|||||||
while (Stack.Num() > 0)
|
while (Stack.Num() > 0)
|
||||||
{
|
{
|
||||||
const int32 Current = Stack.Last();
|
const int32 Current = Stack.Last();
|
||||||
|
OutFinalCell = Current;
|
||||||
if (Current == Goal)
|
if (Current == Goal)
|
||||||
{
|
{
|
||||||
InOutReport.bCanReachDeparture = true;
|
InOutReport.bCanReachDeparture = true;
|
||||||
@@ -2501,6 +2538,7 @@ namespace VoxelStrateMeasurePrivate
|
|||||||
RecordTraversal(Current, Neighbour);
|
RecordTraversal(Current, Neighbour);
|
||||||
Stack.Add(Neighbour);
|
Stack.Add(Neighbour);
|
||||||
NextDirection.Add(0);
|
NextDirection.Add(0);
|
||||||
|
OutLastReachedCell = Neighbour;
|
||||||
bAdvanced = true;
|
bAdvanced = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2913,6 +2951,8 @@ bool VF_MeasurePlayerFitWalkWithSampler(
|
|||||||
&PlayerFitMask);
|
&PlayerFitMask);
|
||||||
|
|
||||||
FVoxelConnectivityDiagnostics Diagnostics;
|
FVoxelConnectivityDiagnostics Diagnostics;
|
||||||
|
TArray<int32> Components;
|
||||||
|
int32 NumComponents = 0;
|
||||||
PopulateConnectivityDiagnostics(
|
PopulateConnectivityDiagnostics(
|
||||||
Grid,
|
Grid,
|
||||||
Start,
|
Start,
|
||||||
@@ -2922,7 +2962,9 @@ bool VF_MeasurePlayerFitWalkWithSampler(
|
|||||||
OutReport.bGoalSnapped,
|
OutReport.bGoalSnapped,
|
||||||
NumRouteRetries,
|
NumRouteRetries,
|
||||||
Diagnostics,
|
Diagnostics,
|
||||||
&PlayerFitMask);
|
&PlayerFitMask,
|
||||||
|
&Components,
|
||||||
|
&NumComponents);
|
||||||
|
|
||||||
OutReport.bValid = true;
|
OutReport.bValid = true;
|
||||||
OutReport.Result = Result;
|
OutReport.Result = Result;
|
||||||
@@ -2943,9 +2985,16 @@ bool VF_MeasurePlayerFitWalkWithSampler(
|
|||||||
? static_cast<float>(static_cast<double>(OutReport.ReachablePlayerFitCells)
|
? static_cast<float>(static_cast<double>(OutReport.ReachablePlayerFitCells)
|
||||||
/ static_cast<double>(OutReport.PlayerFitVolumeCells))
|
/ static_cast<double>(OutReport.PlayerFitVolumeCells))
|
||||||
: 0.0f;
|
: 0.0f;
|
||||||
|
OutReport.PlayerFitComponents = NumComponents;
|
||||||
|
OutReport.LargestPlayerFitComponentCells = Diagnostics.LargestComponentCells;
|
||||||
|
OutReport.ArrivalComponentCells = Diagnostics.StartComponentCells;
|
||||||
|
OutReport.DepartureComponentCells = Diagnostics.GoalComponentCells;
|
||||||
|
OutReport.MouthComponentGapVoxels = Diagnostics.StartToGoalComponentDistanceCells;
|
||||||
OutReport.StraightLineMeters = FVector::Dist(AVoxel, BVoxel)
|
OutReport.StraightLineMeters = FVector::Dist(AVoxel, BVoxel)
|
||||||
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters;
|
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters;
|
||||||
|
|
||||||
|
int32 AgentFinalCell = INDEX_NONE;
|
||||||
|
int32 LastReachedCell = INDEX_NONE;
|
||||||
if (Start >= 0 && Goal >= 0)
|
if (Start >= 0 && Goal >= 0)
|
||||||
{
|
{
|
||||||
const float NarrowGapThresholdVoxels = OutReport.NarrowGapThresholdMeters
|
const float NarrowGapThresholdVoxels = OutReport.NarrowGapThresholdMeters
|
||||||
@@ -2956,7 +3005,127 @@ bool VF_MeasurePlayerFitWalkWithSampler(
|
|||||||
Start,
|
Start,
|
||||||
Goal,
|
Goal,
|
||||||
NarrowGapThresholdVoxels,
|
NarrowGapThresholdVoxels,
|
||||||
OutReport);
|
OutReport,
|
||||||
|
AgentFinalCell,
|
||||||
|
LastReachedCell);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto RecordCellPosition = [&Grid](
|
||||||
|
int32 Cell,
|
||||||
|
bool& bOutHasPosition,
|
||||||
|
FVector& OutPosition)
|
||||||
|
{
|
||||||
|
if (Cell >= 0 && Cell < Grid.CellCount)
|
||||||
|
{
|
||||||
|
int32 X = 0;
|
||||||
|
int32 Y = 0;
|
||||||
|
int32 Z = 0;
|
||||||
|
DecodeIndex(Grid, Cell, X, Y, Z);
|
||||||
|
bOutHasPosition = true;
|
||||||
|
OutPosition = Grid.CellCenter(X, Y, Z);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
RecordCellPosition(
|
||||||
|
AgentFinalCell,
|
||||||
|
OutReport.bHasAgentFinalPosition,
|
||||||
|
OutReport.AgentFinalVoxels);
|
||||||
|
RecordCellPosition(
|
||||||
|
LastReachedCell,
|
||||||
|
OutReport.bHasLastReachedPosition,
|
||||||
|
OutReport.LastReachedVoxels);
|
||||||
|
|
||||||
|
const int32 StartComponent = Start >= 0 && Components.IsValidIndex(Start)
|
||||||
|
? Components[Start] : INDEX_NONE;
|
||||||
|
const int32 GoalComponent = Goal >= 0 && Components.IsValidIndex(Goal)
|
||||||
|
? Components[Goal] : INDEX_NONE;
|
||||||
|
if (StartComponent >= 0)
|
||||||
|
{
|
||||||
|
float BestOriginDistanceSquared = FLT_MAX;
|
||||||
|
for (int32 Cell = 0; Cell < Grid.CellCount; ++Cell)
|
||||||
|
{
|
||||||
|
if (!Components.IsValidIndex(Cell) || Components[Cell] != StartComponent)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int32 X = 0;
|
||||||
|
int32 Y = 0;
|
||||||
|
int32 Z = 0;
|
||||||
|
DecodeIndex(Grid, Cell, X, Y, Z);
|
||||||
|
const FVector Position = Grid.CellCenter(X, Y, Z);
|
||||||
|
BestOriginDistanceSquared = FMath::Min(
|
||||||
|
BestOriginDistanceSquared,
|
||||||
|
Position.X * Position.X + Position.Y * Position.Y);
|
||||||
|
}
|
||||||
|
if (BestOriginDistanceSquared < FLT_MAX)
|
||||||
|
{
|
||||||
|
OutReport.ReachableSetToOriginColumnVoxels = FMath::Sqrt(
|
||||||
|
BestOriginDistanceSquared);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool bOriginPointInWindow = 0.0f >= Grid.MinX && 0.0f < Grid.MaxX
|
||||||
|
&& 0.0f >= Grid.MinY && 0.0f < Grid.MaxY;
|
||||||
|
if (bOriginPointInWindow)
|
||||||
|
{
|
||||||
|
const int32 OriginX = FMath::Clamp(
|
||||||
|
FMath::FloorToInt((0.0f - Grid.MinX) / static_cast<float>(Grid.SampleStep)),
|
||||||
|
0,
|
||||||
|
Grid.NumX - 1);
|
||||||
|
const int32 OriginY = FMath::Clamp(
|
||||||
|
FMath::FloorToInt((0.0f - Grid.MinY) / static_cast<float>(Grid.SampleStep)),
|
||||||
|
0,
|
||||||
|
Grid.NumY - 1);
|
||||||
|
OutReport.bOriginColumnInSampledWindow = true;
|
||||||
|
for (int32 Z = 0; Z < Grid.NumZ; ++Z)
|
||||||
|
{
|
||||||
|
const int32 Cell = Grid.Index(OriginX, OriginY, Z);
|
||||||
|
if (PlayerFitMask[Cell] == 0u)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++OutReport.OriginColumnPlayerFitCells;
|
||||||
|
OutReport.bOriginColumnHasPlayerFit = true;
|
||||||
|
if (Components.IsValidIndex(Cell) && Components[Cell] == StartComponent)
|
||||||
|
{
|
||||||
|
OutReport.bOriginColumnReachable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AgentFinalCell >= 0 && AgentFinalCell < Grid.CellCount && GoalComponent >= 0)
|
||||||
|
{
|
||||||
|
const FVector AgentPosition = OutReport.AgentFinalVoxels;
|
||||||
|
float BestTargetDistanceSquared = FLT_MAX;
|
||||||
|
int32 BestTargetCell = INDEX_NONE;
|
||||||
|
for (int32 Cell = 0; Cell < Grid.CellCount; ++Cell)
|
||||||
|
{
|
||||||
|
if (!Components.IsValidIndex(Cell) || Components[Cell] != GoalComponent)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int32 X = 0;
|
||||||
|
int32 Y = 0;
|
||||||
|
int32 Z = 0;
|
||||||
|
DecodeIndex(Grid, Cell, X, Y, Z);
|
||||||
|
const float DistanceSquared = FVector::DistSquared(
|
||||||
|
AgentPosition, Grid.CellCenter(X, Y, Z));
|
||||||
|
if (DistanceSquared < BestTargetDistanceSquared
|
||||||
|
|| (DistanceSquared == BestTargetDistanceSquared
|
||||||
|
&& (BestTargetCell == INDEX_NONE || Cell < BestTargetCell)))
|
||||||
|
{
|
||||||
|
BestTargetDistanceSquared = DistanceSquared;
|
||||||
|
BestTargetCell = Cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (BestTargetCell >= 0)
|
||||||
|
{
|
||||||
|
RecordCellPosition(
|
||||||
|
BestTargetCell,
|
||||||
|
OutReport.bHasTargetComponentNearestCell,
|
||||||
|
OutReport.TargetComponentNearestVoxels);
|
||||||
|
OutReport.AgentToTargetComponentGapVoxels = FMath::Sqrt(
|
||||||
|
BestTargetDistanceSquared);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OutReport.bCanReachDeparture = Result == EVoxelConnectivityResult::Connected;
|
OutReport.bCanReachDeparture = Result == EVoxelConnectivityResult::Connected;
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ struct VOXELFORGE_API FVoxelStrateMeasureSettings
|
|||||||
// this enabled for that topology; an over-cap route window is then refused honestly instead
|
// this enabled for that topology; an over-cap route window is then refused honestly instead
|
||||||
// of being cropped back to a mouth-only box.
|
// of being cropped back to a mouth-only box.
|
||||||
bool bIncludeOriginInCoverWindow = false;
|
bool bIncludeOriginInCoverWindow = false;
|
||||||
|
// Diagnostic-only strengthening of the preceding policy: ensure the sampled box contains an
|
||||||
|
// actual one-voxel column around (0,0), rather than ending exactly at the exclusive upper
|
||||||
|
// bound when both mouths lie on one side of the origin.
|
||||||
|
bool bForceOriginColumnInCoverWindow = false;
|
||||||
int32 MaxCells = 8000000; // Refuse a grid larger than this many cells.
|
int32 MaxCells = 8000000; // Refuse a grid larger than this many cells.
|
||||||
int32 MaxRouteRetries = 16; // Alternate coarse routes to full-resolution-check after the first route.
|
int32 MaxRouteRetries = 16; // Alternate coarse routes to full-resolution-check after the first route.
|
||||||
int32 HeadroomCells = 2; // Legacy walkable-column metric only.
|
int32 HeadroomCells = 2; // Legacy walkable-column metric only.
|
||||||
@@ -308,6 +312,34 @@ struct VOXELFORGE_API FVoxelPlayerFitWalkReport
|
|||||||
float CapsuleWidthMeters = 0.0f;
|
float CapsuleWidthMeters = 0.0f;
|
||||||
float CapsuleHeightMeters = 0.0f;
|
float CapsuleHeightMeters = 0.0f;
|
||||||
FString NarrowGapDefinition;
|
FString NarrowGapDefinition;
|
||||||
|
|
||||||
|
// Component facts are retained from the same fit-mask flood fill that drives the route
|
||||||
|
// verdict. They are exposed here so an editor diagnostic does not need to sample the field
|
||||||
|
// a second time just to explain a failed mouth-to-mouth walk.
|
||||||
|
int32 PlayerFitComponents = 0;
|
||||||
|
int64 LargestPlayerFitComponentCells = 0;
|
||||||
|
int64 ArrivalComponentCells = 0;
|
||||||
|
int64 DepartureComponentCells = 0;
|
||||||
|
float MouthComponentGapVoxels = -1.0f;
|
||||||
|
|
||||||
|
// The DFS returns to its root when the target component is unreachable. Keep both the
|
||||||
|
// physical end of that walk and the last newly reached pose: the latter is the useful
|
||||||
|
// failure-frontier point for a render, while the former is the literal final pose.
|
||||||
|
bool bHasAgentFinalPosition = false;
|
||||||
|
FVector AgentFinalVoxels = FVector::ZeroVector;
|
||||||
|
bool bHasLastReachedPosition = false;
|
||||||
|
FVector LastReachedVoxels = FVector::ZeroVector;
|
||||||
|
bool bHasTargetComponentNearestCell = false;
|
||||||
|
FVector TargetComponentNearestVoxels = FVector::ZeroVector;
|
||||||
|
float AgentToTargetComponentGapVoxels = -1.0f;
|
||||||
|
|
||||||
|
// Origin checks are facts about the sampled fit mask. An origin-inclusive secondary probe
|
||||||
|
// can populate these even when the primary mouth-sized window deliberately excludes (0,0).
|
||||||
|
bool bOriginColumnInSampledWindow = false;
|
||||||
|
bool bOriginColumnHasPlayerFit = false;
|
||||||
|
int64 OriginColumnPlayerFitCells = 0;
|
||||||
|
bool bOriginColumnReachable = false;
|
||||||
|
float ReachableSetToOriginColumnVoxels = -1.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ struct FExploreArguments
|
|||||||
bool bRender = true;
|
bool bRender = true;
|
||||||
bool bWalk = true;
|
bool bWalk = true;
|
||||||
bool bExport = true;
|
bool bExport = true;
|
||||||
|
bool bFailureFocusRender = false;
|
||||||
FString OutDirectory;
|
FString OutDirectory;
|
||||||
|
|
||||||
int32 RenderWidth = DefaultRenderWidth;
|
int32 RenderWidth = DefaultRenderWidth;
|
||||||
@@ -205,6 +206,9 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
|
|||||||
FParse::Value(*Params, TEXT("rendermaxdistance="), OutArguments.RenderMaxDistanceVoxels);
|
FParse::Value(*Params, TEXT("rendermaxdistance="), OutArguments.RenderMaxDistanceVoxels);
|
||||||
FParse::Value(*Params, TEXT("exportsize="), OutArguments.ExportSize);
|
FParse::Value(*Params, TEXT("exportsize="), OutArguments.ExportSize);
|
||||||
FParse::Value(*Params, TEXT("maxwalkcells="), OutArguments.MaxWalkCells);
|
FParse::Value(*Params, TEXT("maxwalkcells="), OutArguments.MaxWalkCells);
|
||||||
|
int32 FailureFocusRender = 0;
|
||||||
|
FParse::Value(*Params, TEXT("failurefocus="), FailureFocusRender);
|
||||||
|
OutArguments.bFailureFocusRender = FailureFocusRender != 0;
|
||||||
|
|
||||||
if (!ParseArchetype(ArchetypeText, OutArguments.Archetype))
|
if (!ParseArchetype(ArchetypeText, OutArguments.Archetype))
|
||||||
{
|
{
|
||||||
@@ -467,10 +471,12 @@ private:
|
|||||||
struct FExploreRenderFrame
|
struct FExploreRenderFrame
|
||||||
{
|
{
|
||||||
FString FileName;
|
FString FileName;
|
||||||
|
FString Purpose = TEXT("overview");
|
||||||
FVector CameraVoxels = FVector::ZeroVector;
|
FVector CameraVoxels = FVector::ZeroVector;
|
||||||
FVector TargetVoxels = FVector::ZeroVector;
|
FVector TargetVoxels = FVector::ZeroVector;
|
||||||
int32 HitPixels = 0;
|
int32 HitPixels = 0;
|
||||||
bool bScaleMarkerProjected = false;
|
bool bScaleMarkerProjected = false;
|
||||||
|
bool bFailureMarkersProjected = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FExploreRenderOutput
|
struct FExploreRenderOutput
|
||||||
@@ -500,6 +506,10 @@ struct FExploreWalkOutput
|
|||||||
FVector DepartureVoxels = FVector::ZeroVector;
|
FVector DepartureVoxels = FVector::ZeroVector;
|
||||||
FString WindowPolicy;
|
FString WindowPolicy;
|
||||||
FVoxelPlayerFitWalkReport Report;
|
FVoxelPlayerFitWalkReport Report;
|
||||||
|
bool bOriginCheckAttempted = false;
|
||||||
|
bool bOriginCheckAvailable = false;
|
||||||
|
FString OriginCheckRefusalReason;
|
||||||
|
FVoxelPlayerFitWalkReport OriginCheckReport;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FExploreExportOutput
|
struct FExploreExportOutput
|
||||||
@@ -990,6 +1000,257 @@ bool RunRender(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DrawPointMarker(
|
||||||
|
TArray<FColor>& Pixels,
|
||||||
|
int32 Width,
|
||||||
|
int32 Height,
|
||||||
|
const FVector& Point,
|
||||||
|
const FVector& CameraPosition,
|
||||||
|
const FExploreCameraBasis& Basis,
|
||||||
|
const FColor& Color,
|
||||||
|
bool& bOutProjected)
|
||||||
|
{
|
||||||
|
FIntPoint Pixel = FIntPoint::ZeroValue;
|
||||||
|
bOutProjected = ProjectPoint(
|
||||||
|
Point, CameraPosition, Basis, Width, Height, Pixel);
|
||||||
|
if (!bOutProjected)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int32 MarkerRadius = 7;
|
||||||
|
DrawLine(
|
||||||
|
Pixels, Width, Height,
|
||||||
|
Pixel.X - MarkerRadius, Pixel.Y,
|
||||||
|
Pixel.X + MarkerRadius, Pixel.Y,
|
||||||
|
Color);
|
||||||
|
DrawLine(
|
||||||
|
Pixels, Width, Height,
|
||||||
|
Pixel.X, Pixel.Y - MarkerRadius,
|
||||||
|
Pixel.X, Pixel.Y + MarkerRadius,
|
||||||
|
Color);
|
||||||
|
DrawLine(
|
||||||
|
Pixels, Width, Height,
|
||||||
|
Pixel.X - 4, Pixel.Y - 4,
|
||||||
|
Pixel.X + 4, Pixel.Y + 4,
|
||||||
|
Color);
|
||||||
|
DrawLine(
|
||||||
|
Pixels, Width, Height,
|
||||||
|
Pixel.X - 4, Pixel.Y + 4,
|
||||||
|
Pixel.X + 4, Pixel.Y - 4,
|
||||||
|
Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RunFailureBoundaryRender(
|
||||||
|
const FExploreArguments& Arguments,
|
||||||
|
const FExploreWorld& World,
|
||||||
|
const FExploreWalkOutput& Walk,
|
||||||
|
FExploreRenderOutput& InOutOutput)
|
||||||
|
{
|
||||||
|
const FVoxelPlayerFitWalkReport& Report = Walk.Report;
|
||||||
|
if (!Report.bHasAgentFinalPosition || !Report.bHasTargetComponentNearestCell)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FVector AgentFinal = Report.AgentFinalVoxels;
|
||||||
|
const FVector TargetComponentCell = Report.TargetComponentNearestVoxels;
|
||||||
|
const FVector LastReached = Report.LastReachedVoxels;
|
||||||
|
const bool bHaveLastReached = Report.bHasLastReachedPosition;
|
||||||
|
const FVector Focus = (AgentFinal + TargetComponentCell) * 0.5f;
|
||||||
|
const FVector Segment = TargetComponentCell - AgentFinal;
|
||||||
|
const FVector SegmentDirection = Segment.GetSafeNormal();
|
||||||
|
if (SegmentDirection.IsNearlyZero())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FVector ViewSide = FVector::CrossProduct(SegmentDirection, FVector::UpVector).GetSafeNormal();
|
||||||
|
if (ViewSide.IsNearlyZero())
|
||||||
|
{
|
||||||
|
ViewSide = FVector::CrossProduct(SegmentDirection, FVector::RightVector).GetSafeNormal();
|
||||||
|
}
|
||||||
|
const float SegmentLength = Segment.Size();
|
||||||
|
const float ViewSideOffset = FMath::Min(64.0f, FMath::Max(16.0f, 0.25f * SegmentLength));
|
||||||
|
const FVector CameraCandidates[][3] = {
|
||||||
|
{
|
||||||
|
AgentFinal - SegmentDirection * 4.0f,
|
||||||
|
bHaveLastReached ? LastReached : AgentFinal,
|
||||||
|
AgentFinal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TargetComponentCell + SegmentDirection * 4.0f,
|
||||||
|
TargetComponentCell,
|
||||||
|
TargetComponentCell,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const FVector ViewTargets[] = {
|
||||||
|
Focus + ViewSide * ViewSideOffset,
|
||||||
|
Focus - ViewSide * ViewSideOffset,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int32 ViewIndex = 0; ViewIndex < UE_ARRAY_COUNT(ViewTargets); ++ViewIndex)
|
||||||
|
{
|
||||||
|
FVector CameraPosition = CameraCandidates[ViewIndex][2];
|
||||||
|
for (int32 CandidateIndex = 0; CandidateIndex < 3; ++CandidateIndex)
|
||||||
|
{
|
||||||
|
const FVector Candidate = CameraCandidates[ViewIndex][CandidateIndex];
|
||||||
|
const float Density = World.Generator->GetDensityAt(Candidate.X, Candidate.Y, Candidate.Z);
|
||||||
|
if (FMath::IsFinite(Density) && Density > 0.0f)
|
||||||
|
{
|
||||||
|
CameraPosition = Candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const float FailureMaxDistanceVoxels = FMath::Max(
|
||||||
|
Arguments.RenderMaxDistanceVoxels,
|
||||||
|
FMath::Max(
|
||||||
|
FVector::Dist(CameraPosition, AgentFinal),
|
||||||
|
FVector::Dist(CameraPosition, TargetComponentCell)) + 32.0f);
|
||||||
|
FExploreCameraBasis Basis;
|
||||||
|
if (!BuildCameraBasis(
|
||||||
|
CameraPosition,
|
||||||
|
ViewTargets[ViewIndex],
|
||||||
|
70.0f,
|
||||||
|
Arguments.RenderWidth,
|
||||||
|
Arguments.RenderHeight,
|
||||||
|
Basis))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FColor> Pixels;
|
||||||
|
Pixels.Init(FColor(7, 11, 18, 255), Arguments.RenderWidth * Arguments.RenderHeight);
|
||||||
|
int32 HitPixels = 0;
|
||||||
|
const FVector LightDirection = FVector(-0.35f, -0.45f, 0.82f).GetSafeNormal();
|
||||||
|
for (int32 PixelY = 0; PixelY < Arguments.RenderHeight; ++PixelY)
|
||||||
|
{
|
||||||
|
for (int32 PixelX = 0; PixelX < Arguments.RenderWidth; ++PixelX)
|
||||||
|
{
|
||||||
|
const FVector RayDirection = MakeRayDirection(
|
||||||
|
Basis,
|
||||||
|
PixelX,
|
||||||
|
PixelY,
|
||||||
|
Arguments.RenderWidth,
|
||||||
|
Arguments.RenderHeight);
|
||||||
|
FVector HitPoint;
|
||||||
|
FVector Normal;
|
||||||
|
if (!TraceDensityRay(
|
||||||
|
*World.Generator,
|
||||||
|
CameraPosition,
|
||||||
|
RayDirection,
|
||||||
|
Arguments.RenderStepVoxels,
|
||||||
|
FailureMaxDistanceVoxels,
|
||||||
|
InOutOutput.BisectionIterations,
|
||||||
|
HitPoint,
|
||||||
|
Normal))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
++HitPixels;
|
||||||
|
const float Diffuse = FMath::Max(0.0f, FVector::DotProduct(Normal, LightDirection));
|
||||||
|
const float Brightness = 0.18f + 0.82f * Diffuse;
|
||||||
|
const uint8 RockR = static_cast<uint8>(FMath::Clamp(54.0f * Brightness, 0.0f, 255.0f));
|
||||||
|
const uint8 RockG = static_cast<uint8>(FMath::Clamp(72.0f * Brightness, 0.0f, 255.0f));
|
||||||
|
const uint8 RockB = static_cast<uint8>(FMath::Clamp(92.0f * Brightness, 0.0f, 255.0f));
|
||||||
|
PutPixel(
|
||||||
|
Pixels,
|
||||||
|
Arguments.RenderWidth,
|
||||||
|
Arguments.RenderHeight,
|
||||||
|
PixelX,
|
||||||
|
PixelY,
|
||||||
|
FColor(RockR, RockG, RockB, 255));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bAgentProjected = false;
|
||||||
|
bool bTargetProjected = false;
|
||||||
|
bool bLastReachedProjected = false;
|
||||||
|
DrawPointMarker(
|
||||||
|
Pixels,
|
||||||
|
Arguments.RenderWidth,
|
||||||
|
Arguments.RenderHeight,
|
||||||
|
AgentFinal,
|
||||||
|
CameraPosition,
|
||||||
|
Basis,
|
||||||
|
FColor(242, 72, 72, 255),
|
||||||
|
bAgentProjected);
|
||||||
|
DrawPointMarker(
|
||||||
|
Pixels,
|
||||||
|
Arguments.RenderWidth,
|
||||||
|
Arguments.RenderHeight,
|
||||||
|
TargetComponentCell,
|
||||||
|
CameraPosition,
|
||||||
|
Basis,
|
||||||
|
FColor(72, 242, 112, 255),
|
||||||
|
bTargetProjected);
|
||||||
|
if (bHaveLastReached)
|
||||||
|
{
|
||||||
|
DrawPointMarker(
|
||||||
|
Pixels,
|
||||||
|
Arguments.RenderWidth,
|
||||||
|
Arguments.RenderHeight,
|
||||||
|
LastReached,
|
||||||
|
CameraPosition,
|
||||||
|
Basis,
|
||||||
|
FColor(242, 176, 56, 255),
|
||||||
|
bLastReachedProjected);
|
||||||
|
}
|
||||||
|
|
||||||
|
FIntPoint AgentPixel = FIntPoint::ZeroValue;
|
||||||
|
FIntPoint TargetPixel = FIntPoint::ZeroValue;
|
||||||
|
if (ProjectPoint(
|
||||||
|
AgentFinal,
|
||||||
|
CameraPosition,
|
||||||
|
Basis,
|
||||||
|
Arguments.RenderWidth,
|
||||||
|
Arguments.RenderHeight,
|
||||||
|
AgentPixel)
|
||||||
|
&& ProjectPoint(
|
||||||
|
TargetComponentCell,
|
||||||
|
CameraPosition,
|
||||||
|
Basis,
|
||||||
|
Arguments.RenderWidth,
|
||||||
|
Arguments.RenderHeight,
|
||||||
|
TargetPixel))
|
||||||
|
{
|
||||||
|
DrawLine(
|
||||||
|
Pixels,
|
||||||
|
Arguments.RenderWidth,
|
||||||
|
Arguments.RenderHeight,
|
||||||
|
AgentPixel.X,
|
||||||
|
AgentPixel.Y,
|
||||||
|
TargetPixel.X,
|
||||||
|
TargetPixel.Y,
|
||||||
|
FColor(244, 214, 72, 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
FExploreRenderFrame& Frame = InOutOutput.Frames.AddDefaulted_GetRef();
|
||||||
|
Frame.FileName = FString::Printf(TEXT("failure_boundary_%02d.png"), ViewIndex);
|
||||||
|
Frame.Purpose = TEXT("agent_final_red_target_component_nearest_green_last_reached_orange");
|
||||||
|
Frame.CameraVoxels = CameraPosition;
|
||||||
|
Frame.TargetVoxels = ViewTargets[ViewIndex];
|
||||||
|
Frame.HitPixels = HitPixels;
|
||||||
|
Frame.bFailureMarkersProjected = bAgentProjected && bTargetProjected
|
||||||
|
&& (!bHaveLastReached || bLastReachedProjected);
|
||||||
|
|
||||||
|
FString Error;
|
||||||
|
if (!SavePng(
|
||||||
|
FPaths::Combine(Arguments.OutDirectory, Frame.FileName),
|
||||||
|
Pixels,
|
||||||
|
Arguments.RenderWidth,
|
||||||
|
Arguments.RenderHeight,
|
||||||
|
Error))
|
||||||
|
{
|
||||||
|
InOutOutput.Status = TEXT("error");
|
||||||
|
InOutOutput.RefusalReason = Error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool RunWalk(
|
bool RunWalk(
|
||||||
const FExploreArguments& Arguments,
|
const FExploreArguments& Arguments,
|
||||||
const FExploreWorld& World,
|
const FExploreWorld& World,
|
||||||
@@ -1054,6 +1315,30 @@ bool RunWalk(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Part C is a separate observation window. Keep the Part A mouth-sized result untouched,
|
||||||
|
// then repeat the same exact fit walk with the route window expanded to include (0,0). This
|
||||||
|
// is measurement-only: it changes neither the density field nor any generation parameter.
|
||||||
|
OutOutput.bOriginCheckAttempted = true;
|
||||||
|
FVoxelStrateMeasureSettings OriginSettings = Settings;
|
||||||
|
OriginSettings.bIncludeOriginInCoverWindow = true;
|
||||||
|
OriginSettings.bForceOriginColumnInCoverWindow = true;
|
||||||
|
if (VF_MeasurePlayerFitWalkWithSampler(
|
||||||
|
Sampler,
|
||||||
|
World.TargetBottomWorldZ,
|
||||||
|
World.TargetTopWorldZ,
|
||||||
|
World.TargetBoundarySealThickness,
|
||||||
|
OutOutput.ArrivalVoxels,
|
||||||
|
OutOutput.DepartureVoxels,
|
||||||
|
OriginSettings,
|
||||||
|
OutOutput.OriginCheckReport))
|
||||||
|
{
|
||||||
|
OutOutput.bOriginCheckAvailable = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OutOutput.OriginCheckRefusalReason = OutOutput.OriginCheckReport.RefusalReason;
|
||||||
|
}
|
||||||
|
|
||||||
OutOutput.Status = TEXT("ok");
|
OutOutput.Status = TEXT("ok");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1431,6 +1716,7 @@ FString BuildExploreJson(
|
|||||||
if (Arguments.bWalk) Writer->WriteValue(TEXT("walk"));
|
if (Arguments.bWalk) Writer->WriteValue(TEXT("walk"));
|
||||||
if (Arguments.bExport) Writer->WriteValue(TEXT("export"));
|
if (Arguments.bExport) Writer->WriteValue(TEXT("export"));
|
||||||
Writer->WriteArrayEnd();
|
Writer->WriteArrayEnd();
|
||||||
|
Writer->WriteValue(TEXT("failure_focus_render"), Arguments.bFailureFocusRender);
|
||||||
Writer->WriteValue(TEXT("out_directory"), Arguments.OutDirectory);
|
Writer->WriteValue(TEXT("out_directory"), Arguments.OutDirectory);
|
||||||
Writer->WriteValue(TEXT("canonical_invocation"), FString::Printf(
|
Writer->WriteValue(TEXT("canonical_invocation"), FString::Printf(
|
||||||
TEXT("UnrealEditor-Cmd VoxelM.uproject -run=VoxelForgeExplore -seed=%d -archetype=%s "
|
TEXT("UnrealEditor-Cmd VoxelM.uproject -run=VoxelForgeExplore -seed=%d -archetype=%s "
|
||||||
@@ -1477,11 +1763,13 @@ FString BuildExploreJson(
|
|||||||
for (const FExploreRenderFrame& Frame : Output.Render.Frames)
|
for (const FExploreRenderFrame& Frame : Output.Render.Frames)
|
||||||
{
|
{
|
||||||
Writer->WriteObjectStart();
|
Writer->WriteObjectStart();
|
||||||
|
Writer->WriteValue(TEXT("purpose"), Frame.Purpose);
|
||||||
Writer->WriteValue(TEXT("file"), Frame.FileName);
|
Writer->WriteValue(TEXT("file"), Frame.FileName);
|
||||||
WriteJsonVector(*Writer, TEXT("camera_voxels"), Frame.CameraVoxels, 1.0f);
|
WriteJsonVector(*Writer, TEXT("camera_voxels"), Frame.CameraVoxels, 1.0f);
|
||||||
WriteJsonVector(*Writer, TEXT("target_voxels"), Frame.TargetVoxels, 1.0f);
|
WriteJsonVector(*Writer, TEXT("target_voxels"), Frame.TargetVoxels, 1.0f);
|
||||||
Writer->WriteValue(TEXT("hit_pixels"), Frame.HitPixels);
|
Writer->WriteValue(TEXT("hit_pixels"), Frame.HitPixels);
|
||||||
Writer->WriteValue(TEXT("scale_marker_projected"), Frame.bScaleMarkerProjected);
|
Writer->WriteValue(TEXT("scale_marker_projected"), Frame.bScaleMarkerProjected);
|
||||||
|
Writer->WriteValue(TEXT("failure_markers_projected"), Frame.bFailureMarkersProjected);
|
||||||
Writer->WriteValue(TEXT("scale_marker_height_m"), 1.76);
|
Writer->WriteValue(TEXT("scale_marker_height_m"), 1.76);
|
||||||
Writer->WriteObjectEnd();
|
Writer->WriteObjectEnd();
|
||||||
}
|
}
|
||||||
@@ -1550,6 +1838,90 @@ FString BuildExploreJson(
|
|||||||
Writer->WriteValue(TEXT("narrow_gap_fraction"), static_cast<double>(Report.NarrowGapFraction));
|
Writer->WriteValue(TEXT("narrow_gap_fraction"), static_cast<double>(Report.NarrowGapFraction));
|
||||||
Writer->WriteValue(TEXT("narrow_gap_events_per_100m"), static_cast<double>(Report.NarrowGapEventsPer100m));
|
Writer->WriteValue(TEXT("narrow_gap_events_per_100m"), static_cast<double>(Report.NarrowGapEventsPer100m));
|
||||||
Writer->WriteValue(TEXT("narrow_gap_definition"), Report.NarrowGapDefinition);
|
Writer->WriteValue(TEXT("narrow_gap_definition"), Report.NarrowGapDefinition);
|
||||||
|
Writer->WriteValue(TEXT("player_fit_components"), Report.PlayerFitComponents);
|
||||||
|
Writer->WriteValue(TEXT("largest_player_fit_component_cells"), Report.LargestPlayerFitComponentCells);
|
||||||
|
Writer->WriteValue(TEXT("arrival_component_cells"), Report.ArrivalComponentCells);
|
||||||
|
Writer->WriteValue(TEXT("departure_component_cells"), Report.DepartureComponentCells);
|
||||||
|
Writer->WriteValue(TEXT("mouth_component_gap_voxels"), static_cast<double>(Report.MouthComponentGapVoxels));
|
||||||
|
Writer->WriteValue(TEXT("mouth_component_gap_m"), static_cast<double>(Report.MouthComponentGapVoxels)
|
||||||
|
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters);
|
||||||
|
Writer->WriteValue(TEXT("agent_final_position_available"), Report.bHasAgentFinalPosition);
|
||||||
|
if (Report.bHasAgentFinalPosition)
|
||||||
|
{
|
||||||
|
WriteJsonVector(*Writer, TEXT("agent_final_position_voxels"), Report.AgentFinalVoxels, 1.0f);
|
||||||
|
WriteJsonVector(*Writer, TEXT("agent_final_position_metres"), Report.AgentFinalVoxels, 0.25f);
|
||||||
|
}
|
||||||
|
Writer->WriteValue(TEXT("last_reached_position_available"), Report.bHasLastReachedPosition);
|
||||||
|
if (Report.bHasLastReachedPosition)
|
||||||
|
{
|
||||||
|
WriteJsonVector(*Writer, TEXT("last_reached_position_voxels"), Report.LastReachedVoxels, 1.0f);
|
||||||
|
WriteJsonVector(*Writer, TEXT("last_reached_position_metres"), Report.LastReachedVoxels, 0.25f);
|
||||||
|
}
|
||||||
|
Writer->WriteValue(TEXT("target_component_nearest_cell_available"), Report.bHasTargetComponentNearestCell);
|
||||||
|
if (Report.bHasTargetComponentNearestCell)
|
||||||
|
{
|
||||||
|
WriteJsonVector(*Writer, TEXT("target_component_nearest_cell_voxels"),
|
||||||
|
Report.TargetComponentNearestVoxels, 1.0f);
|
||||||
|
WriteJsonVector(*Writer, TEXT("target_component_nearest_cell_metres"),
|
||||||
|
Report.TargetComponentNearestVoxels, 0.25f);
|
||||||
|
}
|
||||||
|
Writer->WriteValue(TEXT("agent_to_target_component_gap_voxels"),
|
||||||
|
static_cast<double>(Report.AgentToTargetComponentGapVoxels));
|
||||||
|
Writer->WriteValue(TEXT("agent_to_target_component_gap_m"),
|
||||||
|
static_cast<double>(Report.AgentToTargetComponentGapVoxels)
|
||||||
|
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters);
|
||||||
|
Writer->WriteValue(TEXT("origin_column_in_sampled_window"), Report.bOriginColumnInSampledWindow);
|
||||||
|
Writer->WriteValue(TEXT("origin_column_has_player_fit"), Report.bOriginColumnHasPlayerFit);
|
||||||
|
Writer->WriteValue(TEXT("origin_column_player_fit_cells"), Report.OriginColumnPlayerFitCells);
|
||||||
|
Writer->WriteValue(TEXT("origin_column_reachable_from_arrival"), Report.bOriginColumnReachable);
|
||||||
|
Writer->WriteValue(TEXT("reachable_set_to_origin_column_voxels"),
|
||||||
|
static_cast<double>(Report.ReachableSetToOriginColumnVoxels));
|
||||||
|
Writer->WriteValue(TEXT("reachable_set_to_origin_column_m"),
|
||||||
|
static_cast<double>(Report.ReachableSetToOriginColumnVoxels)
|
||||||
|
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters);
|
||||||
|
Writer->WriteObjectStart(TEXT("origin_check"));
|
||||||
|
Writer->WriteValue(TEXT("attempted"), Output.Walk.bOriginCheckAttempted);
|
||||||
|
Writer->WriteValue(TEXT("available"), Output.Walk.bOriginCheckAvailable);
|
||||||
|
Writer->WriteValue(TEXT("refusal_or_error"), Output.Walk.OriginCheckRefusalReason);
|
||||||
|
if (Output.Walk.bOriginCheckAvailable)
|
||||||
|
{
|
||||||
|
const FVoxelPlayerFitWalkReport& OriginReport = Output.Walk.OriginCheckReport;
|
||||||
|
Writer->WriteValue(TEXT("window_policy"), TEXT("origin-inclusive mouth-AABB + margin"));
|
||||||
|
Writer->WriteValue(TEXT("connectivity_result"), ConnectivityResultName(OriginReport.Result));
|
||||||
|
Writer->WriteValue(TEXT("can_reach_departure"), OriginReport.bCanReachDeparture);
|
||||||
|
Writer->WriteValue(TEXT("sampled_num_x"), OriginReport.SampledNumX);
|
||||||
|
Writer->WriteValue(TEXT("sampled_num_y"), OriginReport.SampledNumY);
|
||||||
|
Writer->WriteValue(TEXT("sampled_num_z"), OriginReport.SampledNumZ);
|
||||||
|
Writer->WriteValue(TEXT("sampled_min_x"), static_cast<double>(OriginReport.SampledMinX));
|
||||||
|
Writer->WriteValue(TEXT("sampled_max_x_exclusive"), static_cast<double>(OriginReport.SampledMaxX));
|
||||||
|
Writer->WriteValue(TEXT("sampled_min_y"), static_cast<double>(OriginReport.SampledMinY));
|
||||||
|
Writer->WriteValue(TEXT("sampled_max_y_exclusive"), static_cast<double>(OriginReport.SampledMaxY));
|
||||||
|
Writer->WriteValue(TEXT("sampled_min_z"), OriginReport.SampledMinZ);
|
||||||
|
Writer->WriteValue(TEXT("sampled_max_z_exclusive"), OriginReport.SampledMaxZ);
|
||||||
|
Writer->WriteValue(TEXT("player_fit_cells"), OriginReport.PlayerFitVolumeCells);
|
||||||
|
Writer->WriteValue(TEXT("reachable_player_fit_cells"), OriginReport.ReachablePlayerFitCells);
|
||||||
|
Writer->WriteValue(TEXT("reachable_player_fit_fraction"),
|
||||||
|
static_cast<double>(OriginReport.ReachablePlayerFitFraction));
|
||||||
|
Writer->WriteValue(TEXT("player_fit_components"), OriginReport.PlayerFitComponents);
|
||||||
|
Writer->WriteValue(TEXT("arrival_component_cells"), OriginReport.ArrivalComponentCells);
|
||||||
|
Writer->WriteValue(TEXT("departure_component_cells"), OriginReport.DepartureComponentCells);
|
||||||
|
Writer->WriteValue(TEXT("mouth_component_gap_voxels"),
|
||||||
|
static_cast<double>(OriginReport.MouthComponentGapVoxels));
|
||||||
|
Writer->WriteValue(TEXT("origin_column_in_sampled_window"),
|
||||||
|
OriginReport.bOriginColumnInSampledWindow);
|
||||||
|
Writer->WriteValue(TEXT("origin_column_has_player_fit"),
|
||||||
|
OriginReport.bOriginColumnHasPlayerFit);
|
||||||
|
Writer->WriteValue(TEXT("origin_column_player_fit_cells"),
|
||||||
|
OriginReport.OriginColumnPlayerFitCells);
|
||||||
|
Writer->WriteValue(TEXT("origin_column_reachable_from_arrival"),
|
||||||
|
OriginReport.bOriginColumnReachable);
|
||||||
|
Writer->WriteValue(TEXT("reachable_set_to_origin_column_voxels"),
|
||||||
|
static_cast<double>(OriginReport.ReachableSetToOriginColumnVoxels));
|
||||||
|
Writer->WriteValue(TEXT("reachable_set_to_origin_column_m"),
|
||||||
|
static_cast<double>(OriginReport.ReachableSetToOriginColumnVoxels)
|
||||||
|
* FVoxelPlayerCapsuleConstants::VoxelSizeMeters);
|
||||||
|
}
|
||||||
|
Writer->WriteObjectEnd();
|
||||||
Writer->WriteObjectEnd();
|
Writer->WriteObjectEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1652,6 +2024,21 @@ int32 UVoxelForgeExploreCommandlet::Main(const FString& Params)
|
|||||||
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeExplore] walk %.3fs (%s)"),
|
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeExplore] walk %.3fs (%s)"),
|
||||||
FPlatformTime::Seconds() - Start, *Output.Walk.Status);
|
FPlatformTime::Seconds() - Start, *Output.Walk.Status);
|
||||||
}
|
}
|
||||||
|
if (Arguments.bFailureFocusRender && Arguments.bRender && Arguments.bWalk)
|
||||||
|
{
|
||||||
|
const double Start = FPlatformTime::Seconds();
|
||||||
|
if (!RunFailureBoundaryRender(Arguments, World, Output.Walk, Output.Render))
|
||||||
|
{
|
||||||
|
bRequestedModeFailed = true;
|
||||||
|
UE_LOG(LogTemp, Error,
|
||||||
|
TEXT("[VoxelForgeExplore] failure-focus render could not produce a boundary image."));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Display, TEXT("[VoxelForgeExplore] failure-focus render %.3fs (ok)"),
|
||||||
|
FPlatformTime::Seconds() - Start);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (Arguments.bExport)
|
if (Arguments.bExport)
|
||||||
{
|
{
|
||||||
const double Start = FPlatformTime::Seconds();
|
const double Start = FPlatformTime::Seconds();
|
||||||
|
|||||||
Reference in new issue
Block a user