wip(step3): 12.5 cm voxels, fall-through fixed, brick evaluator in game, organic tunnels -- speed gate FAILS
Stopped by the owner's decision (in-game lit captures were eating the round); he judges the look in game instead. 1. Fall-through with the research field: fixed. Deep drop, gate on: 2 holds (0.50 s), min foot clearance +43 cm; gate off: penetrates (-278 cm). Stress fixture offsets converted to metres. 2. VOXEL_SIZE 25 -> 12.5 cm. Physical sizes kept in metres (player, steps, gate, insets, research field sizes, dig radii, test thresholds); class defaults that count chunks/voxels doubled (ClipRadius 6, RenderDistanceChunks 1536, ViewDistance*, StrateViewMargin 6, PawnCollisionRadius 2, SkirtCells 4, EdgeSealThickness 128, decoration spacings, density volume res 256). Asset values override these defaults: the owner must double his own. Ring scale, six workers, idle, step-2f field before the look changes: 64 m cube (519^3) 4.55 s; 32 m cube (259^3) 0.95 s. 3. In game: tiles go through the brick evaluator (proved by a log marker); profile switch rebuilds the loaded world (~5,800 tiles); full-detail fill 12.4 s after spawn in the visible-window test. 4. Look: round winding tunnels, per-column ellipsoid/lattice caches. All step-2f geometry gates pass at 12.5 cm (0 crumbs, 1 solid component per profile, solid share 69-87%). 259^3 ring, six workers: 1.08-2.02 s (worst HyperComplex) -- the 600 ms gate FAILS. Lit captures failed (hidden/off-screen window, then blank frames); not pursued. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
ca78352e43
commit
bc0f9ee791
33 files changed
+2691
-803
No files matched your search
@@ -603,27 +603,27 @@ namespace
|
||||
|
||||
FString VF_FormatWorldScaleAudit()
|
||||
{
|
||||
constexpr float VoxelMetres = 0.25f;
|
||||
constexpr float StrateHeightVoxels = 8.0f * static_cast<float>(CHUNK_SIZE);
|
||||
constexpr float VoxelMetres = VOXEL_SIZE / 100.0f;
|
||||
constexpr float StrateHeightVoxels = 16.0f * static_cast<float>(CHUNK_SIZE);
|
||||
FString Report = TEXT(
|
||||
"WORLD SCALE AUDIT (all distances are authored in voxels; METRES = voxels * 0.25; "
|
||||
"player radius=1.36 vox/0.34 m, height=7.04 vox/1.76 m; frequency rows show nominal "
|
||||
"WORLD SCALE AUDIT (all distances are authored in voxels; METRES = voxels * VOXEL_SIZE; "
|
||||
"player radius=2.72 vox/0.34 m, height=14.08 vox/1.76 m; frequency rows show nominal "
|
||||
"period=1/frequency)\n"
|
||||
"archetype | name | voxels | METRES | body/level meaning\n");
|
||||
auto Add = [&Report](const TCHAR* Archetype, const TCHAR* Name,
|
||||
auto Add = [&Report, VoxelMetres](const TCHAR* Archetype, const TCHAR* Name,
|
||||
float Voxels, const TCHAR* Meaning)
|
||||
{
|
||||
Report += FString::Printf(
|
||||
TEXT("%s | %s | %.2f | %.2f | %s\n"), Archetype, Name,
|
||||
Voxels, Voxels * 0.25f, Meaning);
|
||||
Voxels, Voxels * VoxelMetres, Meaning);
|
||||
};
|
||||
auto AddRange = [&Report](const TCHAR* Archetype, const TCHAR* Name,
|
||||
auto AddRange = [&Report, VoxelMetres](const TCHAR* Archetype, const TCHAR* Name,
|
||||
float MinVoxels, float MaxVoxels,
|
||||
const TCHAR* Meaning)
|
||||
{
|
||||
Report += FString::Printf(
|
||||
TEXT("%s | %s | %.2f..%.2f | %.2f..%.2f | %s\n"), Archetype, Name,
|
||||
MinVoxels, MaxVoxels, MinVoxels * 0.25f, MaxVoxels * 0.25f, Meaning);
|
||||
MinVoxels, MaxVoxels, MinVoxels * VoxelMetres, MaxVoxels * VoxelMetres, Meaning);
|
||||
};
|
||||
const TCHAR* TunnelNames[] = { TEXT("TunnelNetwork"), TEXT("Underwater") };
|
||||
for (const TCHAR* Archetype : TunnelNames)
|
||||
@@ -708,7 +708,7 @@ namespace
|
||||
{
|
||||
Add(Archetype, TEXT("StrateHeight"), StrateHeightVoxels,
|
||||
TEXT("8 chunks / 64 m: open vertical chamber envelope"));
|
||||
Add(Archetype, TEXT("Floor height"), StrateHeightVoxels * 0.25f,
|
||||
Add(Archetype, TEXT("Floor height"), StrateHeightVoxels * VoxelMetres,
|
||||
TEXT("16 m above the strate bottom"));
|
||||
Add(Archetype, TEXT("Ceiling height"), StrateHeightVoxels * 0.60f,
|
||||
TEXT("38.4 m above the bottom; 22.4 m clear span before roughness"));
|
||||
@@ -728,7 +728,7 @@ namespace
|
||||
TEXT("1 m solid boundary"));
|
||||
}
|
||||
|
||||
Add(TEXT("Common layout"), TEXT("OriginSpineRadius"), 14.0f,
|
||||
Add(TEXT("Common layout"), TEXT("OriginSpineRadius"), 28.0f,
|
||||
TEXT("3.5 m radius / 7 m diameter central descent spine"));
|
||||
Add(TEXT("Common layout"), TEXT("InterStrateGap"), 0.0f,
|
||||
TEXT("0 m bedrock gap by default; configured as whole 8 m chunks"));
|
||||
@@ -750,7 +750,7 @@ namespace
|
||||
TEXT("8 chunks / 64 m: high sky-cap envelope"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("ElevationRange"), 80.0f,
|
||||
TEXT("20 m terrain relief: human-scale hills, not a miniature room"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("Base ground height"), StrateHeightVoxels * 0.25f,
|
||||
Add(TEXT("SurfaceWorld"), TEXT("Base ground height"), StrateHeightVoxels * VoxelMetres,
|
||||
TEXT("16 m mean ground elevation above the strate bottom"));
|
||||
Add(TEXT("SurfaceWorld"), TEXT("HeightWarpStrength"), 48.0f,
|
||||
TEXT("12 m horizontal landform distortion"));
|
||||
@@ -1002,7 +1002,7 @@ namespace
|
||||
D.StartToGoalComponentDistanceCells,
|
||||
*Law, FloorAreaM2,
|
||||
Found->Metrics.WalkableFloorAreaFraction,
|
||||
Found->Metrics.MedianVerticalClearance * 0.25f,
|
||||
Found->Metrics.MedianVerticalClearance * (VOXEL_SIZE / 100.0f),
|
||||
VF_ShowcaseRoughnessFeatureRatio(Found->Candidate));
|
||||
}
|
||||
return Report;
|
||||
|
||||
@@ -202,7 +202,7 @@ namespace
|
||||
Passage->LowerLanding.StandingPoint.Y,
|
||||
Passage->LowerLanding.FloorZ));
|
||||
|
||||
constexpr float MaxStationSpacing = 0.5f;
|
||||
constexpr float MaxStationSpacing = 1.0f;
|
||||
constexpr float PlayerWidthVoxels =
|
||||
VoxelPassageGeometry::WalkCorridorPlayerWidthVoxels;
|
||||
constexpr float PlayerHalfWidthVoxels = PlayerWidthVoxels * 0.5f;
|
||||
@@ -214,11 +214,11 @@ namespace
|
||||
PlayerWidthVoxels / static_cast<float>(NumLateralSamples - 1);
|
||||
constexpr int32 NumVerticalSamples = 14;
|
||||
constexpr int32 FloorProbeIntervals = 128;
|
||||
constexpr float FloorProbeHalfRange = 32.0f;
|
||||
constexpr float FloorProbeHalfRange = 64.0f;
|
||||
constexpr float FloorProbeStep =
|
||||
(2.0f * FloorProbeHalfRange) / static_cast<float>(FloorProbeIntervals);
|
||||
constexpr float FirstBodySampleZ = 0.25f;
|
||||
constexpr float BodySampleSpacing = 0.5f;
|
||||
constexpr float FirstBodySampleZ = 0.5f;
|
||||
constexpr float BodySampleSpacing = 1.0f;
|
||||
OutSamples.ProofStencilTileOrigins.Reset();
|
||||
OutSamples.ProofStencilUniformTiles = 0;
|
||||
OutSamples.ProofStencilSealedTiles = 0;
|
||||
@@ -484,7 +484,7 @@ namespace
|
||||
{
|
||||
constexpr float GapProfileMinOffset = -360.0f;
|
||||
constexpr float GapProfileMaxOffset = 8.0f;
|
||||
constexpr float GapProfileStep = 0.5f;
|
||||
constexpr float GapProfileStep = 1.0f;
|
||||
constexpr int32 GapProfileSamples = 737;
|
||||
TArray<float> GapProfileDensities;
|
||||
GapProfileDensities.SetNumUninitialized(GapProfileSamples);
|
||||
@@ -878,7 +878,7 @@ namespace
|
||||
const float HorizontalDistance = FMath::Sqrt(
|
||||
FMath::Square(Blocker.X - ClosestX)
|
||||
+ FMath::Square(Blocker.Y - ClosestY));
|
||||
if (HorizontalDistance <= 2.0f)
|
||||
if (HorizontalDistance <= 4.0f)
|
||||
{
|
||||
const float FloorZ = FMath::Lerp(
|
||||
Candidate.NativeFloorProfileZ[SegmentIndex],
|
||||
@@ -1004,7 +1004,7 @@ namespace
|
||||
const float HorizontalDistance = FMath::Sqrt(
|
||||
FMath::Square(Blocker.X - ClosestX)
|
||||
+ FMath::Square(Blocker.Y - ClosestY));
|
||||
if (HorizontalDistance <= 12.0f)
|
||||
if (HorizontalDistance <= 24.0f)
|
||||
{
|
||||
const float FloorZ = FMath::Lerp(
|
||||
Candidate.NativeFloorProfileZ[SegmentIndex],
|
||||
|
||||
@@ -98,18 +98,21 @@ namespace
|
||||
float ExpectedFloorZ,
|
||||
float& OutFloorZ)
|
||||
{
|
||||
constexpr float SampleStep = 0.25f;
|
||||
constexpr float SampleStep = VoxelPassageGeometry::MetersToVoxels(0.0625f);
|
||||
const float ExpectedAt = Generator.GetDensityAt(
|
||||
Centreline.X, Centreline.Y, ExpectedFloorZ);
|
||||
const float ExpectedBelow = Generator.GetDensityAt(
|
||||
Centreline.X, Centreline.Y, ExpectedFloorZ - 0.75f);
|
||||
Centreline.X, Centreline.Y, ExpectedFloorZ
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.1875f));
|
||||
const float ExpectedAbove = Generator.GetDensityAt(
|
||||
Centreline.X, Centreline.Y, ExpectedFloorZ + 0.75f);
|
||||
Centreline.X, Centreline.Y, ExpectedFloorZ
|
||||
+ VoxelPassageGeometry::MetersToVoxels(0.1875f));
|
||||
// Search around the authored tunnel floor, not from an arbitrary lower surface. A source
|
||||
// archetype may contain another cave floor below the tunnel; that is not the floor carried
|
||||
// by this passage and must not hijack the audit.
|
||||
const float StartZ = ExpectedFloorZ - 4.0f;
|
||||
const float EndZ = ExpectedFloorZ + 4.0f;
|
||||
const float ProbeHalfRange = VoxelPassageGeometry::MetersToVoxels(1.0f);
|
||||
const float StartZ = ExpectedFloorZ - ProbeHalfRange;
|
||||
const float EndZ = ExpectedFloorZ + ProbeHalfRange;
|
||||
const int32 NumSteps = FMath::Max(
|
||||
1, FMath::CeilToInt((EndZ - StartZ) / SampleStep));
|
||||
|
||||
@@ -157,15 +160,18 @@ namespace
|
||||
PreviousDensity = CurrentDensity;
|
||||
}
|
||||
if (BestDistance == FLT_MAX
|
||||
|| BestDistance > VoxelPassageGeometry::LandingFloorThicknessVoxels + 1.0f)
|
||||
|| BestDistance > VoxelPassageGeometry::LandingFloorThicknessVoxels
|
||||
+ VoxelPassageGeometry::MetersToVoxels(0.25f))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const float Below = Generator.GetDensityAt(
|
||||
Centreline.X, Centreline.Y, BestCrossingZ - 0.75f);
|
||||
Centreline.X, Centreline.Y, BestCrossingZ
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.1875f));
|
||||
const float Above = Generator.GetDensityAt(
|
||||
Centreline.X, Centreline.Y, BestCrossingZ + 0.75f);
|
||||
Centreline.X, Centreline.Y, BestCrossingZ
|
||||
+ VoxelPassageGeometry::MetersToVoxels(0.1875f));
|
||||
if (!FMath::IsFinite(Below) || !FMath::IsFinite(Above)
|
||||
|| !(Below < 0.0f) || !(Above > 0.0f))
|
||||
{
|
||||
@@ -1115,8 +1121,9 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
EndIndex == 0 ? TEXT("upper") : TEXT("lower"), StrateIndex));
|
||||
}
|
||||
|
||||
const bool bWidthAndHeadroom = Landing.HalfWidth * 2.0f >= 12.0f - 0.01f
|
||||
&& Landing.CeilingZ - Landing.FloorZ >= 12.0f - 0.01f;
|
||||
const float ThreeMetresVoxels = VoxelPassageGeometry::MetersToVoxels(3.0f);
|
||||
const bool bWidthAndHeadroom = Landing.HalfWidth * 2.0f >= ThreeMetresVoxels - 0.01f
|
||||
&& Landing.CeilingZ - Landing.FloorZ >= ThreeMetresVoxels - 0.01f;
|
||||
const bool bDoorTangent = FMath::IsNearlyEqual(
|
||||
Landing.DoorPoint.Z - Landing.FloorZ,
|
||||
Passage.Radius >= 0.0f ? FMath::Max(
|
||||
@@ -1136,10 +1143,10 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
|
||||
const FVector FloorSamples[] = {
|
||||
Landing.StandingPoint,
|
||||
Landing.StandingPoint + FVector(2.0f, 0.0f, 0.0f),
|
||||
Landing.StandingPoint + FVector(-2.0f, 0.0f, 0.0f),
|
||||
Landing.StandingPoint + FVector(0.0f, 2.0f, 0.0f),
|
||||
Landing.StandingPoint + FVector(0.0f, -2.0f, 0.0f) };
|
||||
Landing.StandingPoint + FVector(VoxelPassageGeometry::MetersToVoxels(0.5f), 0.0f, 0.0f),
|
||||
Landing.StandingPoint + FVector(-VoxelPassageGeometry::MetersToVoxels(0.5f), 0.0f, 0.0f),
|
||||
Landing.StandingPoint + FVector(0.0f, VoxelPassageGeometry::MetersToVoxels(0.5f), 0.0f),
|
||||
Landing.StandingPoint + FVector(0.0f, -VoxelPassageGeometry::MetersToVoxels(0.5f), 0.0f) };
|
||||
float SurfaceHeights[UE_ARRAY_COUNT(FloorSamples)] = {};
|
||||
bool bFloorPassed = true;
|
||||
float LandingGradient = -1.0f;
|
||||
@@ -1147,9 +1154,11 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
const FVector& Sample = FloorSamples[SampleIndex];
|
||||
const float Below = World.Generator->GetDensityAt(
|
||||
Sample.X, Sample.Y, Landing.FloorZ - 1.0f);
|
||||
Sample.X, Sample.Y, Landing.FloorZ
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.25f));
|
||||
const float Above = World.Generator->GetDensityAt(
|
||||
Sample.X, Sample.Y, Landing.FloorZ + 1.0f);
|
||||
Sample.X, Sample.Y, Landing.FloorZ
|
||||
+ VoxelPassageGeometry::MetersToVoxels(0.25f));
|
||||
const float Denominator = Above - Below;
|
||||
if (!FMath::IsFinite(Below) || !FMath::IsFinite(Above)
|
||||
|| !(Below < 0.0f) || !(Above >= 0.0f)
|
||||
@@ -1158,8 +1167,10 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
bFloorPassed = false;
|
||||
continue;
|
||||
}
|
||||
SurfaceHeights[SampleIndex] = Landing.FloorZ - 1.0f
|
||||
+ FMath::Clamp(-Below / Denominator, 0.0f, 1.0f) * 2.0f;
|
||||
const float FloorProbeHalfRange = VoxelPassageGeometry::MetersToVoxels(0.25f);
|
||||
SurfaceHeights[SampleIndex] = Landing.FloorZ - FloorProbeHalfRange
|
||||
+ FMath::Clamp(-Below / Denominator, 0.0f, 1.0f)
|
||||
* 2.0f * FloorProbeHalfRange;
|
||||
}
|
||||
|
||||
if (bFloorPassed)
|
||||
@@ -1168,7 +1179,8 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
for (int32 SampleIndex = 1; SampleIndex < UE_ARRAY_COUNT(FloorSamples); ++SampleIndex)
|
||||
{
|
||||
MaxGradient = FMath::Max(MaxGradient,
|
||||
FMath::Abs(SurfaceHeights[SampleIndex] - SurfaceHeights[0]) / 2.0f);
|
||||
FMath::Abs(SurfaceHeights[SampleIndex] - SurfaceHeights[0])
|
||||
/ VoxelPassageGeometry::MetersToVoxels(0.5f));
|
||||
}
|
||||
LandingGradient = MaxGradient;
|
||||
WorstLandingFloorGradient = FMath::Max(WorstLandingFloorGradient, MaxGradient);
|
||||
|
||||
@@ -968,17 +968,17 @@ bool FVoxelForgeScaleDiagnosisTest::RunTest(const FString& Parameters)
|
||||
"core_without_structural_posts +ray=%.3f vox (%.3f m); "
|
||||
"-ray final=%.3f vox (%.3f m), -ray core=%.3f vox (%.3f m)"),
|
||||
DiagnosisSeed, MazeCandidateIndex, MazeSlot, *MazeAxisDescription,
|
||||
MazeParams.CorridorRadius, MazeParams.CorridorRadius * 0.25f,
|
||||
MazeParams.CorridorRadius * 2.0f * 0.25f,
|
||||
MazeParams.CorridorRadius, MazeParams.CorridorRadius * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
|
||||
MazeParams.CorridorRadius * 2.0f * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
|
||||
MazeFinal.Positive.CrossingRadiusVoxels,
|
||||
MazeFinal.Positive.CrossingRadiusVoxels * 0.25f,
|
||||
MazeFinal.Positive.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
|
||||
MazeFinal.Positive.FirstSolidStep,
|
||||
MazeCore.Positive.CrossingRadiusVoxels,
|
||||
MazeCore.Positive.CrossingRadiusVoxels * 0.25f,
|
||||
MazeCore.Positive.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
|
||||
MazeFinal.Negative.CrossingRadiusVoxels,
|
||||
MazeFinal.Negative.CrossingRadiusVoxels * 0.25f,
|
||||
MazeFinal.Negative.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
|
||||
MazeCore.Negative.CrossingRadiusVoxels,
|
||||
MazeCore.Negative.CrossingRadiusVoxels * 0.25f));
|
||||
MazeCore.Negative.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters));
|
||||
}
|
||||
|
||||
// TunnelNetwork — use the current fixed showcase roll and recover a real cached tunnel
|
||||
@@ -1000,16 +1000,17 @@ bool FVoxelForgeScaleDiagnosisTest::RunTest(const FString& Parameters)
|
||||
"core_without_structural_posts +ray=%.3f vox (%.3f m), -ray=%.3f vox (%.3f m)"),
|
||||
DiagnosisSeed, TunnelCandidateIndex, *TunnelAxis.Description,
|
||||
TunnelParams.TunnelMinRadius, TunnelParams.TunnelMaxRadius,
|
||||
TunnelParams.TunnelMinRadius * 0.25f, TunnelParams.TunnelMaxRadius * 0.25f,
|
||||
TunnelAxis.NominalRadius, TunnelAxis.NominalRadius * 0.25f,
|
||||
TunnelParams.TunnelMinRadius * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
|
||||
TunnelParams.TunnelMaxRadius * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
|
||||
TunnelAxis.NominalRadius, TunnelAxis.NominalRadius * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
|
||||
TunnelAxis.FinalSection.Positive.CrossingRadiusVoxels,
|
||||
TunnelAxis.FinalSection.Positive.CrossingRadiusVoxels * 0.25f,
|
||||
TunnelAxis.FinalSection.Positive.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
|
||||
TunnelAxis.FinalSection.Negative.CrossingRadiusVoxels,
|
||||
TunnelAxis.FinalSection.Negative.CrossingRadiusVoxels * 0.25f,
|
||||
TunnelAxis.FinalSection.Negative.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
|
||||
TunnelAxis.CoreSection.Positive.CrossingRadiusVoxels,
|
||||
TunnelAxis.CoreSection.Positive.CrossingRadiusVoxels * 0.25f,
|
||||
TunnelAxis.CoreSection.Positive.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
|
||||
TunnelAxis.CoreSection.Negative.CrossingRadiusVoxels,
|
||||
TunnelAxis.CoreSection.Negative.CrossingRadiusVoxels * 0.25f));
|
||||
TunnelAxis.CoreSection.Negative.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters));
|
||||
}
|
||||
|
||||
// VerticalShafts — recover the deterministic shaft centre from the same hash contract as
|
||||
@@ -1035,16 +1036,16 @@ bool FVoxelForgeScaleDiagnosisTest::RunTest(const FString& Parameters)
|
||||
"core_without_structural_posts +ray=%.3f vox (%.3f m), -ray=%.3f vox (%.3f m)"),
|
||||
DiagnosisSeed, ShaftCandidateIndex, *ShaftAxisDescription,
|
||||
ShaftParams.ShaftMinRadius, ShaftParams.ShaftMaxRadius,
|
||||
ShaftParams.ShaftMinRadius * 0.25f, ShaftParams.ShaftMaxRadius * 0.25f,
|
||||
ShaftNominalRadius, ShaftNominalRadius * 0.25f,
|
||||
ShaftParams.ShaftMinRadius * (VOXEL_SIZE / 100.0f), ShaftParams.ShaftMaxRadius * (VOXEL_SIZE / 100.0f),
|
||||
ShaftNominalRadius, ShaftNominalRadius * (VOXEL_SIZE / 100.0f),
|
||||
ShaftFinal.Positive.CrossingRadiusVoxels,
|
||||
ShaftFinal.Positive.CrossingRadiusVoxels * 0.25f,
|
||||
ShaftFinal.Positive.CrossingRadiusVoxels * (VOXEL_SIZE / 100.0f),
|
||||
ShaftFinal.Negative.CrossingRadiusVoxels,
|
||||
ShaftFinal.Negative.CrossingRadiusVoxels * 0.25f,
|
||||
ShaftFinal.Negative.CrossingRadiusVoxels * (VOXEL_SIZE / 100.0f),
|
||||
ShaftCore.Positive.CrossingRadiusVoxels,
|
||||
ShaftCore.Positive.CrossingRadiusVoxels * 0.25f,
|
||||
ShaftCore.Positive.CrossingRadiusVoxels * (VOXEL_SIZE / 100.0f),
|
||||
ShaftCore.Negative.CrossingRadiusVoxels,
|
||||
ShaftCore.Negative.CrossingRadiusVoxels * 0.25f));
|
||||
ShaftCore.Negative.CrossingRadiusVoxels * (VOXEL_SIZE / 100.0f)));
|
||||
}
|
||||
|
||||
FVoxelStrateMeasureSettings CharacterFitSettings;
|
||||
@@ -1164,11 +1165,11 @@ bool FVoxelForgeScaleDiagnosisTest::RunTest(const FString& Parameters)
|
||||
"walkable slope<=%.1f deg; capsule clearance is re-tested at resolved support; "
|
||||
"HeadroomCells=%d is legacy-only"),
|
||||
ControlSettings.PlayerCapsuleRadiusVoxels,
|
||||
ControlSettings.PlayerCapsuleRadiusVoxels * 0.25f,
|
||||
ControlSettings.PlayerCapsuleRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
|
||||
ControlSettings.PlayerCapsuleHalfHeightVoxels,
|
||||
ControlSettings.PlayerCapsuleHalfHeightVoxels * 0.25f,
|
||||
ControlSettings.PlayerCapsuleHalfHeightVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
|
||||
FVoxelPlayerCapsuleConstants::HeightVoxels,
|
||||
FVoxelPlayerCapsuleConstants::HeightVoxels * 0.25f,
|
||||
FVoxelPlayerCapsuleConstants::HeightVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
|
||||
FMath::CeilToInt(2.0f * ControlSettings.PlayerCapsuleHalfHeightVoxels),
|
||||
FMath::CeilToInt(2.0f * ControlSettings.PlayerCapsuleHalfHeightVoxels) - 1,
|
||||
ControlSettings.PlayerMaxStepHeightMeters,
|
||||
|
||||
@@ -6778,10 +6778,11 @@ FVoxelPassageLanding VF_BuildPassageLanding(
|
||||
{
|
||||
FVoxelPassageLanding Landing;
|
||||
|
||||
// Body-derived dimensions at the authored 25 cm voxel scale are shared with the origin
|
||||
// Body-derived dimensions at the authored 12.5 cm voxel scale are shared with the origin
|
||||
// landing. In particular, the 0.68 m player diameter, 3 m turning patch, 1.76 m capsule
|
||||
// height and 1 m headroom become the common §6.5 formulas in VoxelPassageGeometry.
|
||||
const float SafeMouthRadius = FMath::Max(FMath::Abs(MouthRadius), 1.0f);
|
||||
const float SafeMouthRadius = FMath::Max(FMath::Abs(MouthRadius),
|
||||
VoxelPassageGeometry::MetersToVoxels(0.25f));
|
||||
const float DesiredHalfWidth = VoxelPassageGeometry::LandingHalfWidthForRadius(MouthRadius);
|
||||
const float DesiredHeight = VoxelPassageGeometry::LandingHeightForRadius(MouthRadius);
|
||||
const float SafeSeal = FMath::Max(BoundarySealThickness, 0.0f);
|
||||
@@ -6793,7 +6794,8 @@ FVoxelPassageLanding VF_BuildPassageLanding(
|
||||
}
|
||||
|
||||
Landing.StandingPoint = InStandingPoint;
|
||||
Landing.FloorZ = InStandingPoint.Z - 0.5f;
|
||||
Landing.FloorZ = InStandingPoint.Z
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.125f);
|
||||
Landing.HalfWidth = DesiredHalfWidth;
|
||||
Landing.CeilingZ = Landing.FloorZ + DesiredHeight;
|
||||
Landing.FloorThickness = VoxelPassageGeometry::LandingFloorThicknessVoxels;
|
||||
@@ -6818,7 +6820,8 @@ FVoxelPassageLanding VF_BuildPassageLanding(
|
||||
if (MaxFloor >= MinFloor)
|
||||
{
|
||||
Landing.FloorZ = FMath::Clamp(Landing.FloorZ, MinFloor, MaxFloor);
|
||||
Landing.StandingPoint.Z = Landing.FloorZ + 0.5f;
|
||||
Landing.StandingPoint.Z = Landing.FloorZ
|
||||
+ VoxelPassageGeometry::MetersToVoxels(0.125f);
|
||||
Landing.CeilingZ = Landing.FloorZ + DesiredHeight;
|
||||
}
|
||||
else
|
||||
@@ -6833,9 +6836,10 @@ FVoxelPassageLanding VF_BuildPassageLanding(
|
||||
}
|
||||
|
||||
// The tube centreline is tangent to the floor: the tube bottom is exactly FloorZ. The door
|
||||
// sits one voxel inside the room so the room/tube smooth union cannot leave a lip.
|
||||
// sits 0.25 m inside the room so the room/tube smooth union cannot leave a lip.
|
||||
Landing.DoorPoint = Landing.StandingPoint
|
||||
+ Direction * FMath::Max(0.0f, Landing.HalfWidth - 1.0f);
|
||||
+ Direction * FMath::Max(0.0f, Landing.HalfWidth
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.25f));
|
||||
Landing.DoorPoint.Z = Landing.FloorZ + SafeMouthRadius;
|
||||
|
||||
return Landing;
|
||||
@@ -6862,11 +6866,14 @@ float VF_EvaluatePassageLandingSDF(
|
||||
Landing.StandingPoint.X,
|
||||
Landing.StandingPoint.Y,
|
||||
(Landing.FloorZ + Landing.CeilingZ) * 0.5f);
|
||||
constexpr float RoomRounding = 1.5f;
|
||||
constexpr float RoomRounding = VoxelPassageGeometry::RoomRoundingVoxels;
|
||||
const FVector RoomHalfExtent(
|
||||
FMath::Max(Landing.HalfWidth - RoomRounding, 0.25f),
|
||||
FMath::Max(Landing.HalfWidth - RoomRounding, 0.25f),
|
||||
FMath::Max((Landing.CeilingZ - Landing.FloorZ) * 0.5f - RoomRounding, 0.25f));
|
||||
FMath::Max(Landing.HalfWidth - RoomRounding,
|
||||
VoxelPassageGeometry::MetersToVoxels(0.0625f)),
|
||||
FMath::Max(Landing.HalfWidth - RoomRounding,
|
||||
VoxelPassageGeometry::MetersToVoxels(0.0625f)),
|
||||
FMath::Max((Landing.CeilingZ - Landing.FloorZ) * 0.5f - RoomRounding,
|
||||
VoxelPassageGeometry::MetersToVoxels(0.0625f)));
|
||||
|
||||
// max(Room, FloorZ-Z) is the important distinction from a bore: below FloorZ is solid,
|
||||
// while every horizontal section above it has the same flat floor plane.
|
||||
@@ -6901,7 +6908,8 @@ bool VF_IsPassageLandingFloor(
|
||||
// three-metre turning patch. The origin room owns its centre, including its support floor.
|
||||
if (IsInFloorBand(Position.Z, Landing.FloorZ, Landing.FloorThickness))
|
||||
{
|
||||
const float FloorHalfWidth = FMath::Max(Landing.HalfWidth - 1.0f, 0.0f);
|
||||
const float FloorHalfWidth = FMath::Max(Landing.HalfWidth
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f);
|
||||
if (FMath::Abs(Position.X - Landing.StandingPoint.X) <= FloorHalfWidth
|
||||
&& FMath::Abs(Position.Y - Landing.StandingPoint.Y) <= FloorHalfWidth)
|
||||
{
|
||||
|
||||
@@ -723,7 +723,7 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
|
||||
{
|
||||
// INTEGER voxel jitter: columns land on integer XY so the generator's surface-column cache
|
||||
// (T1.a, §8.10) applies — FRACTIONAL XY recomputes the noise-heavy heightfield+biome on EVERY
|
||||
// sample. A 25 cm grid offset is imperceptible; random yaw/scale still breaks up the regularity.
|
||||
// sample. A 12.5 cm grid offset is imperceptible; random yaw/scale still breaks up the regularity.
|
||||
const uint32 HJ = DecoHash(Cell.X, Cell.Y, gx, gy, -1, 0, InSeed, 0x10C0u);
|
||||
const int32 JX = FMath::Min(Spacing - 1, (int32)(VoxelHash::ToFloat01(HJ) * (float)Spacing));
|
||||
const int32 JY = FMath::Min(Spacing - 1, (int32)(VoxelHash::ToFloat01(VoxelHash::Mix(HJ ^ 0x68BC21EBu)) * (float)Spacing));
|
||||
|
||||
@@ -145,7 +145,7 @@ void UVoxelDensityVolume::ResumeAfterGenerationChange()
|
||||
|
||||
int32 UVoxelDensityVolume::ResPerAxis() const
|
||||
{
|
||||
return FMath::Clamp(Settings ? Settings->DensityVolumeResolution : 128, 32, 256);
|
||||
return FMath::Clamp(Settings ? Settings->DensityVolumeResolution : 256, 32, 512);
|
||||
}
|
||||
|
||||
int32 UVoxelDensityVolume::NumLevels() const
|
||||
|
||||
@@ -2722,9 +2722,9 @@ void UVoxelGenerator::InitializeSettings(const UVoxelSettings* Settings)
|
||||
// Les paramètres globaux sont copiés ici une seule fois : le chemin voxel ne doit pas
|
||||
// déréférencer l'asset de settings.
|
||||
Seed = Settings ? Settings->GetEffectiveWorldSeed() : 0;
|
||||
OriginSpineRadius = Settings ? Settings->GetEffectiveOriginSpineRadius() : 14.0f;
|
||||
OriginSpineRadius = Settings ? Settings->GetEffectiveOriginSpineRadius() : 28.0f;
|
||||
WorldRadiusVoxels = Settings ? Settings->GetEffectiveWorldRadiusVoxels() : 0.0f;
|
||||
EdgeSealThickness = Settings ? Settings->EdgeSealThickness : 64.0f;
|
||||
EdgeSealThickness = Settings ? Settings->EdgeSealThickness : 128.0f;
|
||||
}
|
||||
|
||||
void UVoxelGenerator::BeginDensityBlock(FIntVector OriginVoxels, int32 Step,
|
||||
|
||||
@@ -4,11 +4,16 @@
|
||||
#include "VoxelMarchingCubesMesher.h"
|
||||
#include "MarchingCubesTables.h"
|
||||
#include "VoxelDensityProfile.h"
|
||||
#if WITH_EDITOR
|
||||
#include "VoxelRedefinedDensityField.h"
|
||||
#endif
|
||||
#include "VoxelStrateManager.h"
|
||||
#include "VoxelTilePostReach.h"
|
||||
#include "VoxelPassageGeometry.h"
|
||||
#include "ProfilingDebugging/CpuProfilerTrace.h"
|
||||
#include "HAL/IConsoleManager.h"
|
||||
#include "Misc/CommandLine.h"
|
||||
#include <atomic>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -703,7 +708,16 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
// gradient) sont échantillonnées — le reste du tampon reste non initialisé et non lu.
|
||||
VoxelDensityProfile::FScopedTimer MesherDensityGridTimer(
|
||||
VoxelDensityProfile::EBucket::MesherDensityGrid);
|
||||
#if WITH_EDITOR
|
||||
const bool bUseParameterizedBrickEvaluator =
|
||||
VoxelForgeRedefinedDensityField::IsParameterizedEnabled()
|
||||
&& !bUseSharedDensityGrid && !bCapturePreEditGrid
|
||||
&& OutCaptureGrid == nullptr;
|
||||
#else
|
||||
constexpr bool bUseParameterizedBrickEvaluator = false;
|
||||
#endif
|
||||
const bool bUseOperatorBlock = !bUseSharedDensityGrid
|
||||
&& !bUseParameterizedBrickEvaluator
|
||||
&& GVoxelForgeUseOperatorBlock != 0;
|
||||
if (bUseOperatorBlock)
|
||||
{
|
||||
@@ -713,7 +727,149 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
OriginVoxels + FIntVector(-Step, -Step, GzLo * Step),
|
||||
Step, MDim, MDim, GzHi - GzLo + 1);
|
||||
}
|
||||
for (int32 gz = GzLo; gz <= GzHi; gz++)
|
||||
if (bUseParameterizedBrickEvaluator)
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
static std::atomic<bool> bLoggedParameterizedBrickRoute{false};
|
||||
if (!bLoggedParameterizedBrickRoute.exchange(true, std::memory_order_relaxed))
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeResearchBrickEvaluator] mesher_grid=1 brick_samples=%d detail_scale=%.6f"),
|
||||
VoxelForgeRedefinedDensityField::ParameterizedBrickSamples,
|
||||
VoxelForgeRedefinedDensityField::GetDetailScale());
|
||||
}
|
||||
constexpr int32 ResearchBrickSamples =
|
||||
VoxelForgeRedefinedDensityField::ParameterizedBrickSamples;
|
||||
const float DetailScale = VoxelForgeRedefinedDensityField::GetDetailScale();
|
||||
TArray<float> DetailBrick;
|
||||
TArray<float> BrickValues;
|
||||
TArray<float> ColumnValues;
|
||||
TArray<uint8> ProofKinds;
|
||||
VoxelForgeRedefinedDensityField::FParameterizedBrickContext BrickContext;
|
||||
const int32 GridZSamples = GzHi - GzLo + 1;
|
||||
const int32 ProofZCount = FMath::DivideAndRoundUp(GridZSamples, ResearchBrickSamples);
|
||||
ProofKinds.SetNumUninitialized(ProofZCount);
|
||||
for (int32 BrickY = 0; BrickY < MDim; BrickY += ResearchBrickSamples)
|
||||
for (int32 BrickX = 0; BrickX < MDim; BrickX += ResearchBrickSamples)
|
||||
{
|
||||
if (ShouldAbortWork())
|
||||
{
|
||||
return FVoxelMeshData();
|
||||
}
|
||||
const int32 BrickSizeX = FMath::Min(ResearchBrickSamples, MDim - BrickX);
|
||||
const int32 BrickSizeY = FMath::Min(ResearchBrickSamples, MDim - BrickY);
|
||||
for (int32 ProofZ = 0; ProofZ < ProofZCount; ++ProofZ)
|
||||
{
|
||||
if (ShouldAbortWork())
|
||||
{
|
||||
return FVoxelMeshData();
|
||||
}
|
||||
const int32 BrickZ = ProofZ * ResearchBrickSamples;
|
||||
const FIntVector ProofSize(
|
||||
BrickSizeX, BrickSizeY,
|
||||
FMath::Min(ResearchBrickSamples, GridZSamples - BrickZ));
|
||||
const FIntVector ProofOrigin = OriginVoxels + FIntVector(
|
||||
(BrickX - 1) * Step,
|
||||
(BrickY - 1) * Step,
|
||||
(GzLo + BrickZ) * Step);
|
||||
|
||||
bool bUniformAir = false;
|
||||
if (VoxelForgeRedefinedDensityField::TryClassifyParameterizedBrick(
|
||||
*Generator, ProofOrigin, ProofSize, bUniformAir, DetailScale,
|
||||
nullptr, nullptr, Step))
|
||||
{
|
||||
ProofKinds[ProofZ] = bUniformAir ? 1 : 2;
|
||||
const float UniformDensity = bUniformAir
|
||||
? VoxelForgeRedefinedDensityField::ParameterizedUniformDensity
|
||||
: -VoxelForgeRedefinedDensityField::ParameterizedUniformDensity;
|
||||
for (int32 Z = 0; Z < ProofSize.Z; ++Z)
|
||||
for (int32 Y = 0; Y < ProofSize.Y; ++Y)
|
||||
for (int32 X = 0; X < ProofSize.X; ++X)
|
||||
{
|
||||
const int32 GX = BrickX + X - 1;
|
||||
const int32 GY = BrickY + Y - 1;
|
||||
const int32 GZ = GzLo + BrickZ + Z;
|
||||
const int32 LocalIndex =
|
||||
((GZ + 1) * MDim + (GY + 1)) * MDim + (GX + 1);
|
||||
DensityGrid[LocalIndex] = UniformDensity;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ProofKinds[ProofZ] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32 ProofZ = 0; ProofZ < ProofZCount;)
|
||||
{
|
||||
if (ProofKinds[ProofZ] != 0)
|
||||
{
|
||||
++ProofZ;
|
||||
continue;
|
||||
}
|
||||
if (ShouldAbortWork())
|
||||
{
|
||||
return FVoxelMeshData();
|
||||
}
|
||||
|
||||
const int32 FirstMixedProofZ = ProofZ;
|
||||
const int32 BrickZ = FirstMixedProofZ * ResearchBrickSamples;
|
||||
const int32 FirstWorldZ = OriginVoxels.Z + (GzLo + BrickZ) * Step;
|
||||
const int32 FirstChunkZ = FMath::FloorToInt(
|
||||
static_cast<float>(FirstWorldZ) / CHUNK_SIZE);
|
||||
int32 RunProofCount = 1;
|
||||
int32 RunSampleCount = FMath::Min(
|
||||
ResearchBrickSamples, GridZSamples - BrickZ);
|
||||
while (ProofZ + RunProofCount < ProofZCount
|
||||
&& ProofKinds[ProofZ + RunProofCount] == 0)
|
||||
{
|
||||
const int32 CandidateSampleCount = FMath::Min(
|
||||
(RunProofCount + 1) * ResearchBrickSamples,
|
||||
GridZSamples - BrickZ);
|
||||
const int32 CandidateEndZ = FirstWorldZ
|
||||
+ (CandidateSampleCount - 1) * Step;
|
||||
const int32 CandidateChunkZ = FMath::FloorToInt(
|
||||
static_cast<float>(CandidateEndZ) / CHUNK_SIZE);
|
||||
if (CandidateSampleCount > FMath::Max(1, CHUNK_SIZE / Step)
|
||||
|| CandidateChunkZ != FirstChunkZ)
|
||||
{
|
||||
break;
|
||||
}
|
||||
++RunProofCount;
|
||||
RunSampleCount = CandidateSampleCount;
|
||||
}
|
||||
|
||||
const FIntVector BrickSize(BrickSizeX, BrickSizeY, RunSampleCount);
|
||||
const FIntVector BrickOrigin = OriginVoxels + FIntVector(
|
||||
(BrickX - 1) * Step,
|
||||
(BrickY - 1) * Step,
|
||||
(GzLo + BrickZ) * Step);
|
||||
VoxelForgeRedefinedDensityField::EvaluateParameterizedDensityBrick(
|
||||
*Generator, BrickOrigin, BrickSize, DetailScale, BrickContext,
|
||||
DetailBrick, ColumnValues, BrickValues, Step);
|
||||
for (int32 Z = 0; Z < BrickSize.Z; ++Z)
|
||||
for (int32 Y = 0; Y < BrickSize.Y; ++Y)
|
||||
for (int32 X = 0; X < BrickSize.X; ++X)
|
||||
{
|
||||
const int32 GX = BrickX + X - 1;
|
||||
const int32 GY = BrickY + Y - 1;
|
||||
const int32 GZ = GzLo + BrickZ + Z;
|
||||
const int32 LocalIndex =
|
||||
((GZ + 1) * MDim + (GY + 1)) * MDim + (GX + 1);
|
||||
DensityGrid[LocalIndex] =
|
||||
BrickValues[(Z * BrickSize.Y + Y) * BrickSize.X + X];
|
||||
}
|
||||
if (OutDensitySampleCount != nullptr)
|
||||
{
|
||||
*OutDensitySampleCount += static_cast<int64>(
|
||||
BrickSize.X * BrickSize.Y * BrickSize.Z);
|
||||
}
|
||||
ProofZ += RunProofCount;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else for (int32 gz = GzLo; gz <= GzHi; gz++)
|
||||
{
|
||||
if (ShouldAbortWork())
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1716,11 +1716,11 @@ namespace
|
||||
float StrateHeightInVoxels)
|
||||
{
|
||||
const float H = FMath::Max(StrateHeightInVoxels, static_cast<float>(CHUNK_SIZE));
|
||||
// Body-derived lattice: a 3 m usable floor needs a 4.286 m clear diameter. At 25 cm per
|
||||
// Body-derived lattice: a 3 m usable floor needs a 4.286 m clear diameter. At 12.5 cm per
|
||||
// voxel that is 17.143 voxels, radius 8.571. The proven 3.75-voxel roughness envelope is
|
||||
// added to the authored radius, then rounded up to 12.5: the worst-case usable floor is
|
||||
// still 0.7 * (2 * (12.5 - 3.75) * 0.25) = 3.0625 m. CellSize stays 64-96 voxels, so
|
||||
// radius remains well below CellSize/2 and a real rock wall survives.
|
||||
// remains 0.7 * (2 * (12.5 - 3.75) * 0.125) = 1.53125 m with the current
|
||||
// voxel-valued authoring. CellSize stays 64-96 voxels (8-12 m), so a rock web remains.
|
||||
P.CellSize = VF_CorpusFreeU(
|
||||
Rng, 64.0f, FMath::Max(64.0f, FMath::Min(96.0f, H * 0.75f)));
|
||||
P.CorridorRadius = VF_CorpusFreeU(
|
||||
|
||||
@@ -2192,11 +2192,14 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
const FVector TunnelLowerDoorDirection = DoorDirection;
|
||||
Passage.LowerLanding.DoorDirection = TunnelLowerDoorDirection;
|
||||
const float UpperDoorOffset = FMath::Max(
|
||||
Passage.UpperLanding.HalfWidth - 1.0f, 0.0f);
|
||||
Passage.UpperLanding.HalfWidth
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f);
|
||||
const float LowerDoorOffset = FMath::Max(
|
||||
Passage.LowerLanding.HalfWidth - 1.0f, 0.0f);
|
||||
Passage.LowerLanding.HalfWidth
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f);
|
||||
const float SafeMouthRadius = FMath::Max(
|
||||
FMath::Abs(Cfg.MouthRadius), 1.0f);
|
||||
FMath::Abs(Cfg.MouthRadius),
|
||||
VoxelPassageGeometry::MetersToVoxels(0.25f));
|
||||
Passage.UpperLanding.DoorPoint =
|
||||
Passage.UpperLanding.StandingPoint + DoorDirection * UpperDoorOffset;
|
||||
Passage.UpperLanding.DoorPoint.Z =
|
||||
@@ -2602,7 +2605,8 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
Entry.PassageType = EVoxelPassageType::VerticalShaft;
|
||||
// Match the reserved future shaft envelope exactly. The entry is only opened through
|
||||
// the top seal into the finite landing room; lower strate seals remain untouched.
|
||||
Entry.Radius = FMath::Max(OriginSpineRadius, 1.0f);
|
||||
Entry.Radius = FMath::Max(OriginSpineRadius,
|
||||
VoxelPassageGeometry::MetersToVoxels(0.25f));
|
||||
Entry.UpperPoint = FVector(0.0f, 0.0f, TopZ + CHUNK_SIZE);
|
||||
// The capsule's lower tangent meets the room ceiling; it never bores through the
|
||||
// room's floor or creates an origin column in strate 0.
|
||||
@@ -3063,9 +3067,11 @@ static bool VF_IsPassageRoomFloor(
|
||||
return Position.Z <= Landing.FloorZ + KINDA_SMALL_NUMBER
|
||||
&& Position.Z > Landing.FloorZ - Landing.FloorThickness
|
||||
&& FMath::Abs(Position.X - Landing.StandingPoint.X)
|
||||
<= FMath::Max(Landing.HalfWidth - 1.0f, 0.0f)
|
||||
<= FMath::Max(Landing.HalfWidth
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f)
|
||||
&& FMath::Abs(Position.Y - Landing.StandingPoint.Y)
|
||||
<= FMath::Max(Landing.HalfWidth - 1.0f, 0.0f);
|
||||
<= FMath::Max(Landing.HalfWidth
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f);
|
||||
}
|
||||
|
||||
void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
|
||||
@@ -3719,10 +3725,12 @@ namespace
|
||||
{
|
||||
continue;
|
||||
}
|
||||
constexpr float RoomRounding = 1.5f;
|
||||
constexpr float RoomRounding = VoxelPassageGeometry::RoomRoundingVoxels;
|
||||
const float Height = Landing->CeilingZ - Landing->FloorZ;
|
||||
const float HalfX = FMath::Max(Landing->HalfWidth - RoomRounding, 0.25f);
|
||||
const float HalfZ = FMath::Max(Height * 0.5f - RoomRounding, 0.25f);
|
||||
const float HalfX = FMath::Max(Landing->HalfWidth - RoomRounding,
|
||||
VoxelPassageGeometry::MetersToVoxels(0.0625f));
|
||||
const float HalfZ = FMath::Max(Height * 0.5f - RoomRounding,
|
||||
VoxelPassageGeometry::MetersToVoxels(0.0625f));
|
||||
const float CenterZ = (Landing->FloorZ + Landing->CeilingZ) * 0.5f;
|
||||
if (!AddDomain(
|
||||
FVector(Landing->StandingPoint.X - HalfX - RoomRounding,
|
||||
@@ -4068,15 +4076,18 @@ namespace
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr float RoomRounding = 1.5f;
|
||||
constexpr float RoomRounding = VoxelPassageGeometry::RoomRoundingVoxels;
|
||||
const float Height = Landing.CeilingZ - Landing.FloorZ;
|
||||
const FVector Center(
|
||||
Landing.StandingPoint.X, Landing.StandingPoint.Y,
|
||||
(Landing.FloorZ + Landing.CeilingZ) * 0.5f);
|
||||
const FVector HalfExtent(
|
||||
FMath::Max(Landing.HalfWidth - RoomRounding, 0.25f),
|
||||
FMath::Max(Landing.HalfWidth - RoomRounding, 0.25f),
|
||||
FMath::Max(Height * 0.5f - RoomRounding, 0.25f));
|
||||
FMath::Max(Landing.HalfWidth - RoomRounding,
|
||||
VoxelPassageGeometry::MetersToVoxels(0.0625f)),
|
||||
FMath::Max(Landing.HalfWidth - RoomRounding,
|
||||
VoxelPassageGeometry::MetersToVoxels(0.0625f)),
|
||||
FMath::Max(Height * 0.5f - RoomRounding,
|
||||
VoxelPassageGeometry::MetersToVoxels(0.0625f)));
|
||||
const FVector OuterMin = Center - HalfExtent - FVector(RoomRounding);
|
||||
const FVector OuterMax = Center + HalfExtent + FVector(RoomRounding);
|
||||
const FVector BoxMin((float)Box.Min.X, (float)Box.Min.Y, (float)Box.Min.Z);
|
||||
@@ -5402,13 +5413,17 @@ bool UVoxelStrateManager::AnyPassageLandingNearLattice(
|
||||
&& Landing->FloorThickness > 0.0f
|
||||
&& VF_LatticeBoxTouchesAABB(
|
||||
VoxelBox, LatticeOrigin, Step,
|
||||
Landing->StandingPoint.X - FMath::Max(Landing->HalfWidth - 1.0f, 0.0f)
|
||||
Landing->StandingPoint.X - FMath::Max(Landing->HalfWidth
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f)
|
||||
- ReachScale,
|
||||
Landing->StandingPoint.X + FMath::Max(Landing->HalfWidth - 1.0f, 0.0f)
|
||||
Landing->StandingPoint.X + FMath::Max(Landing->HalfWidth
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f)
|
||||
+ ReachScale,
|
||||
Landing->StandingPoint.Y - FMath::Max(Landing->HalfWidth - 1.0f, 0.0f)
|
||||
Landing->StandingPoint.Y - FMath::Max(Landing->HalfWidth
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f)
|
||||
- ReachScale,
|
||||
Landing->StandingPoint.Y + FMath::Max(Landing->HalfWidth - 1.0f, 0.0f)
|
||||
Landing->StandingPoint.Y + FMath::Max(Landing->HalfWidth
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f)
|
||||
+ ReachScale,
|
||||
Landing->FloorZ - Landing->FloorThickness - ReachScale,
|
||||
Landing->FloorZ + ReachScale))
|
||||
@@ -5444,10 +5459,12 @@ bool UVoxelStrateManager::AnyPassageLandingNearLattice(
|
||||
{
|
||||
continue;
|
||||
}
|
||||
constexpr float RoomRounding = 1.5f;
|
||||
constexpr float RoomRounding = VoxelPassageGeometry::RoomRoundingVoxels;
|
||||
const float Height = Landing->CeilingZ - Landing->FloorZ;
|
||||
const float HalfX = FMath::Max(Landing->HalfWidth - RoomRounding, 0.25f);
|
||||
const float HalfZ = FMath::Max(Height * 0.5f - RoomRounding, 0.25f);
|
||||
const float HalfX = FMath::Max(Landing->HalfWidth - RoomRounding,
|
||||
VoxelPassageGeometry::MetersToVoxels(0.0625f));
|
||||
const float HalfZ = FMath::Max(Height * 0.5f - RoomRounding,
|
||||
VoxelPassageGeometry::MetersToVoxels(0.0625f));
|
||||
const float CenterZ = (Landing->FloorZ + Landing->CeilingZ) * 0.5f;
|
||||
if (VF_LatticeBoxTouchesAABB(
|
||||
VoxelBox, LatticeOrigin, Step,
|
||||
@@ -5651,10 +5668,12 @@ bool UVoxelStrateManager::AnyPassageCarvingNearLattice(
|
||||
{
|
||||
continue;
|
||||
}
|
||||
constexpr float RoomRounding = 1.5f;
|
||||
constexpr float RoomRounding = VoxelPassageGeometry::RoomRoundingVoxels;
|
||||
const float Height = Landing->CeilingZ - Landing->FloorZ;
|
||||
const float HalfX = FMath::Max(Landing->HalfWidth - RoomRounding, 0.25f);
|
||||
const float HalfZ = FMath::Max(Height * 0.5f - RoomRounding, 0.25f);
|
||||
const float HalfX = FMath::Max(Landing->HalfWidth - RoomRounding,
|
||||
VoxelPassageGeometry::MetersToVoxels(0.0625f));
|
||||
const float HalfZ = FMath::Max(Height * 0.5f - RoomRounding,
|
||||
VoxelPassageGeometry::MetersToVoxels(0.0625f));
|
||||
const float CenterZ = (Landing->FloorZ + Landing->CeilingZ) * 0.5f;
|
||||
if (VF_LatticeBoxTouchesAABB(
|
||||
VoxelBox, LatticeOrigin, Step,
|
||||
@@ -5773,7 +5792,8 @@ bool UVoxelStrateManager::AnyLandingFloorAtLattice(
|
||||
// structural writer, not evidence that the writer is absent.
|
||||
return true;
|
||||
}
|
||||
const float HalfWidth = FMath::Max(Landing.HalfWidth - 1.0f, 0.0f);
|
||||
const float HalfWidth = FMath::Max(Landing.HalfWidth
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f);
|
||||
int32 IX0 = 0, IX1 = -1, IY0 = 0, IY1 = -1, IZ0 = 0, IZ1 = -1;
|
||||
return VF_LatticeAxisRange(
|
||||
FMath::Max((float)VoxelBox.Min.X,
|
||||
@@ -5899,7 +5919,8 @@ bool UVoxelStrateManager::AnyLandingFloorAtLattice(
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float HalfWidth = FMath::Max(Geometry.HalfWidth - 1.0f, 0.0f);
|
||||
const float HalfWidth = FMath::Max(Geometry.HalfWidth
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f);
|
||||
int32 IX0 = 0, IX1 = -1, IY0 = 0, IY1 = -1, IZ0 = 0, IZ1 = -1;
|
||||
if (VF_LatticeAxisRange(
|
||||
FMath::Max((float)VoxelBox.Min.X, -HalfWidth - 0.0f),
|
||||
|
||||
@@ -764,8 +764,8 @@ namespace
|
||||
FVoxelStrateMeasureSettings VF_ComposerMeasureSettings()
|
||||
{
|
||||
FVoxelStrateMeasureSettings Settings;
|
||||
Settings.SampleStep = 4;
|
||||
Settings.RadiusInVoxels = 256;
|
||||
Settings.SampleStep = 8;
|
||||
Settings.RadiusInVoxels = 512;
|
||||
Settings.CenterXY = FVector2D::ZeroVector;
|
||||
Settings.MaxCells = 8000000;
|
||||
Settings.MaxRouteRetries = 16;
|
||||
@@ -1752,6 +1752,26 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
VoxelForgeStartupTrace::Finish(TEXT("end_play_before_steady_state"));
|
||||
}
|
||||
|
||||
if (bResearchGamePerfEnabled)
|
||||
{
|
||||
ResearchGamePerfMovingFrameTimesMs.Sort();
|
||||
const auto PercentileMs = [this](double Percentile) -> double
|
||||
{
|
||||
if (ResearchGamePerfMovingFrameTimesMs.IsEmpty()) return 0.0;
|
||||
const int32 Index = FMath::Clamp(
|
||||
FMath::CeilToInt(Percentile * ResearchGamePerfMovingFrameTimesMs.Num()) - 1,
|
||||
0, ResearchGamePerfMovingFrameTimesMs.Num() - 1);
|
||||
return ResearchGamePerfMovingFrameTimesMs[Index];
|
||||
};
|
||||
const double MaximumMs = ResearchGamePerfMovingFrameTimesMs.IsEmpty()
|
||||
? 0.0 : ResearchGamePerfMovingFrameTimesMs.Last();
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeResearchGamePerf] moving_frames=%d moving_window_s=%.3f "
|
||||
"p50_ms=%.3f p95_ms=%.3f p99_ms=%.3f max_ms=%.3f"),
|
||||
ResearchGamePerfMovingFrameTimesMs.Num(), ResearchGamePerfMovingSeconds,
|
||||
PercentileMs(0.50), PercentileMs(0.95), PercentileMs(0.99), MaximumMs);
|
||||
}
|
||||
|
||||
LogVisualStalenessAudit();
|
||||
GVoxelForgeDigMotionTests.Remove(this);
|
||||
GVoxelForgeVisualGapAudits.Remove(this);
|
||||
@@ -1776,6 +1796,16 @@ void AVoxelWorld::BeginPlay()
|
||||
VoxelForgeRedefinedDensityField::SetParameterSet(ResearchParameterizedCaveSet);
|
||||
}
|
||||
#endif
|
||||
#if WITH_EDITOR
|
||||
AppliedResearchFieldEnabled = VoxelForgeRedefinedDensityField::IsParameterizedEnabled() ? 1 : 0;
|
||||
AppliedResearchFieldProfile = VoxelForgeRedefinedDensityField::GetParameterSet();
|
||||
#endif
|
||||
int32 ResearchGamePerfValue = 0;
|
||||
FParse::Value(FCommandLine::Get(), TEXT("voxel.ResearchGamePerf="), ResearchGamePerfValue);
|
||||
bResearchGamePerfEnabled = ResearchGamePerfValue != 0;
|
||||
ResearchGamePerfLastFrameSeconds = 0.0;
|
||||
ResearchGamePerfMovingSeconds = 0.0;
|
||||
ResearchGamePerfMovingFrameTimesMs.Reset();
|
||||
VoxelForgeStartupTrace::BeginFromCommandLine();
|
||||
VoxelForgeStartupTrace::FStageScope StartupTraceStage(TEXT("BeginPlay"));
|
||||
Super::BeginPlay();
|
||||
@@ -2095,6 +2125,38 @@ void AVoxelWorld::BeginPlay()
|
||||
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCaptureDelaySeconds="),
|
||||
TestCeilingCaptureDelaySeconds);
|
||||
TestCeilingCaptureDelaySeconds = FMath::Max(TestCeilingCaptureDelaySeconds, 1.0f);
|
||||
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingSecondCaptureDelaySeconds="),
|
||||
TestCeilingSecondCaptureDelaySeconds);
|
||||
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCameraMoveSpeedCmPerSecond="),
|
||||
TestCeilingCameraMoveSpeedCmPerSecond);
|
||||
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCameraMoveStartSeconds="),
|
||||
TestCeilingCameraMoveStartSeconds);
|
||||
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCameraMoveDurationSeconds="),
|
||||
TestCeilingCameraMoveDurationSeconds);
|
||||
FString CameraMoveDirectionText;
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCameraMoveDirection="),
|
||||
CameraMoveDirectionText))
|
||||
{
|
||||
TArray<FString> Components;
|
||||
CameraMoveDirectionText.ParseIntoArray(Components, TEXT(","), true);
|
||||
if (Components.Num() == 3)
|
||||
{
|
||||
TestCeilingCameraMoveDirection = FVector(
|
||||
FCString::Atof(*Components[0]), FCString::Atof(*Components[1]),
|
||||
FCString::Atof(*Components[2]));
|
||||
}
|
||||
}
|
||||
TestCeilingCameraMoveDirection = TestCeilingCameraMoveDirection.GetSafeNormal();
|
||||
if (TestCeilingCameraMoveDirection.IsNearlyZero())
|
||||
{
|
||||
TestCeilingCameraMoveDirection = FVector::XAxisVector;
|
||||
}
|
||||
TestCeilingCameraMoveSpeedCmPerSecond = FMath::Max(
|
||||
TestCeilingCameraMoveSpeedCmPerSecond, 0.0f);
|
||||
TestCeilingCameraMoveStartSeconds = FMath::Max(
|
||||
TestCeilingCameraMoveStartSeconds, 0.0f);
|
||||
TestCeilingCameraMoveDurationSeconds = FMath::Max(
|
||||
TestCeilingCameraMoveDurationSeconds, 0.0f);
|
||||
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingWallCloseup="),
|
||||
TestCeilingWallCloseup);
|
||||
TestCeilingViewBeginSeconds = FPlatformTime::Seconds();
|
||||
@@ -2108,6 +2170,32 @@ void AVoxelWorld::BeginPlay()
|
||||
TestCeilingCameraRotation.Yaw, TestCeilingCameraRotation.Roll,
|
||||
HeadlessStreamingTestCenterVoxel.X, HeadlessStreamingTestCenterVoxel.Y,
|
||||
HeadlessStreamingTestCenterVoxel.Z, TestCeilingCaptureDelaySeconds);
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTestCeiling] second_capture_delay_s=%.1f camera_move_cm_s=%.1f "
|
||||
"move_start_s=%.1f move_duration_s=%.1f move_direction=(%.3f,%.3f,%.3f) "
|
||||
"research_game_perf=%d"),
|
||||
TestCeilingSecondCaptureDelaySeconds, TestCeilingCameraMoveSpeedCmPerSecond,
|
||||
TestCeilingCameraMoveStartSeconds, TestCeilingCameraMoveDurationSeconds,
|
||||
TestCeilingCameraMoveDirection.X, TestCeilingCameraMoveDirection.Y,
|
||||
TestCeilingCameraMoveDirection.Z, bResearchGamePerfEnabled ? 1 : 0);
|
||||
}
|
||||
|
||||
TestResearchProfileSwitchAfterSeconds = -1.0f;
|
||||
TestResearchProfileSwitchTo = -1;
|
||||
TestResearchProfileSwitchBeginSeconds = FPlatformTime::Seconds();
|
||||
bTestResearchProfileSwitchConfigured =
|
||||
FParse::Value(FCommandLine::Get(),
|
||||
TEXT("voxel.TestResearchProfileSwitchAfterSeconds="),
|
||||
TestResearchProfileSwitchAfterSeconds)
|
||||
&& FParse::Value(FCommandLine::Get(),
|
||||
TEXT("voxel.TestResearchProfileSwitchTo="), TestResearchProfileSwitchTo)
|
||||
&& TestResearchProfileSwitchAfterSeconds >= 0.0f;
|
||||
bTestResearchProfileSwitchApplied = false;
|
||||
if (bTestResearchProfileSwitchConfigured)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeResearchProfileSwitch] configured=1 after_s=%.3f target_profile=%d"),
|
||||
TestResearchProfileSwitchAfterSeconds, TestResearchProfileSwitchTo);
|
||||
}
|
||||
|
||||
// The owner's current DA_Settings is intentionally a one-strate surface layout, so it cannot
|
||||
@@ -2499,7 +2587,8 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
|
||||
bHeadlessCollisionGateStressTestFailed = false;
|
||||
bHeadlessCollisionGateStressTestExitRequested = false;
|
||||
bHeadlessCollisionGateStressTestGateWasEngaged = false;
|
||||
bHeadlessCollisionGateStressTestHorizontalGateProbeApplied = false;
|
||||
bHeadlessCollisionGateStressTestFullStopProbeApplied = false;
|
||||
bHeadlessCollisionGateStressTestFullStopProbeObserved = false;
|
||||
bHeadlessCollisionGateStressTestFullStop = false;
|
||||
bHeadlessCollisionGateStressTestStartHeightCrossed = false;
|
||||
bHeadlessCollisionGateStressTestTerrainViolation = false;
|
||||
@@ -2521,6 +2610,7 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
|
||||
HeadlessCollisionGateStressTestLandedSeconds = 0.0;
|
||||
HeadlessCollisionGateStressTestGateStartSeconds = 0.0;
|
||||
HeadlessCollisionGateStressTestFullStopStartSeconds = 0.0;
|
||||
HeadlessCollisionGateStressTestFullStopProbeStartSeconds = 0.0;
|
||||
HeadlessCollisionGateStressTestDistanceCm = 0.0;
|
||||
HeadlessCollisionGateStressTestGateDistanceCm = 0.0;
|
||||
HeadlessCollisionGateStressTestTotalGateDurationSeconds = 0.0;
|
||||
@@ -2549,8 +2639,8 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
|
||||
bHeadlessSurfaceFallTestExitRequested = false;
|
||||
bHeadlessSurfaceFallTestPredictionProbeInjected = false;
|
||||
bHeadlessSurfaceFallTestPredictionProbeCleared = false;
|
||||
HeadlessSurfaceFallTestSpawnHeightVoxels = 20.0f;
|
||||
HeadlessSurfaceFallTestGroundToleranceVoxels = 4.0f;
|
||||
HeadlessSurfaceFallTestSpawnHeightVoxels = 5.0f * 100.0f / VOXEL_SIZE;
|
||||
HeadlessSurfaceFallTestGroundToleranceVoxels = 1.0f * 100.0f / VOXEL_SIZE;
|
||||
HeadlessSurfaceFallTestTimeoutSeconds = 8.0f;
|
||||
HeadlessSurfaceFallTestPlayerHalfHeightVoxels =
|
||||
VoxelPassageGeometry::PlayerHalfHeightVoxels;
|
||||
@@ -2715,7 +2805,7 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
|
||||
HeadlessStrateCrossingTestStartDelaySeconds = 1.0f;
|
||||
HeadlessStrateCrossingTestTimeoutSeconds = 120.0f;
|
||||
HeadlessStrateCrossingTestMaxGateSeconds = 10.0f;
|
||||
HeadlessStrateCrossingTestRouteTargetRadiusVoxels = 2.0f;
|
||||
HeadlessStrateCrossingTestRouteTargetRadiusVoxels = 0.5f * 100.0f / VOXEL_SIZE;
|
||||
FParse::Value(CommandLine, TEXT("voxel.TestStrateCrossingSpeedCmPerSecond="),
|
||||
HeadlessStrateCrossingTestSpeedCmPerSecond);
|
||||
FParse::Value(CommandLine, TEXT("voxel.TestStrateCrossingStartDelaySeconds="),
|
||||
@@ -3434,7 +3524,7 @@ void AVoxelWorld::ObserveHeadlessStrateCrossingTest(
|
||||
const float Clearance = FeetZ - PassageFloorZ;
|
||||
HeadlessStrateCrossingTestMinFloorClearanceVoxels = FMath::Min(
|
||||
HeadlessStrateCrossingTestMinFloorClearanceVoxels, Clearance);
|
||||
constexpr float FloorViolationToleranceVoxels = 2.0f;
|
||||
constexpr float FloorViolationToleranceVoxels = 0.5f * 100.0f / VOXEL_SIZE;
|
||||
if (Clearance < -FloorViolationToleranceVoxels)
|
||||
{
|
||||
bHeadlessStrateCrossingTestFailed = true;
|
||||
@@ -3688,8 +3778,9 @@ void AVoxelWorld::AdvanceHeadlessDigMotionTest(FVector& InOutPlayerPosition, APa
|
||||
BestDirection = Index;
|
||||
}
|
||||
}
|
||||
constexpr float DigOffsetVoxels = 8.0f;
|
||||
if (Clearances[BestDirection] < DigOffsetVoxels + 3.0f)
|
||||
constexpr float DigOffsetVoxels = 2.0f * 100.0f / VOXEL_SIZE;
|
||||
constexpr float DigClearanceMarginVoxels = 0.75f * 100.0f / VOXEL_SIZE;
|
||||
if (Clearances[BestDirection] < DigOffsetVoxels + DigClearanceMarginVoxels)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -3996,10 +4087,12 @@ bool AVoxelWorld::MeasureHeadlessCollisionGateStressTerrain(
|
||||
// nearest air/solid zero crossing adjacent to the capsule's lowest point, then linearly
|
||||
// interpolate it. Searching up when the feet sample is solid reports negative clearance
|
||||
// instead of silently choosing a lower floor after a fall-through.
|
||||
// Keep the half-voxel surface search resolution, but double the number of samples after the
|
||||
// voxel size halved so the search retains its original physical reach.
|
||||
constexpr float SearchStepVoxels = 0.5f;
|
||||
const int32 MaxSearchSteps = bHeadlessCollisionGateStressDeepDrop
|
||||
&& !bHeadlessCollisionGateStressDeepDropSurfaceCached
|
||||
? 512 : 256;
|
||||
? 1024 : 512;
|
||||
float LastZ = FeetZ;
|
||||
float LastDensity = OutFeetDensity;
|
||||
const bool bSearchDown = OutFeetDensity >= 0.0f;
|
||||
@@ -4051,6 +4144,13 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
|
||||
}
|
||||
|
||||
const double Now = FPlatformTime::Seconds();
|
||||
if (HeadlessCollisionGateStressTestBeginSeconds <= 0.0)
|
||||
{
|
||||
// Include setup failures in the bounded stress-test lifetime too. Otherwise a research
|
||||
// field with no surface at the default probe can set Failed before the start placement,
|
||||
// leave BeginSeconds at zero, and strand the commandlet game until the outer watchdog.
|
||||
HeadlessCollisionGateStressTestBeginSeconds = Now;
|
||||
}
|
||||
int32 DeepDropTopChunkZ = 0;
|
||||
int32 DeepDropBottomChunkZ = 0;
|
||||
if (bHeadlessCollisionGateStressDeepDrop)
|
||||
@@ -4085,16 +4185,19 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
|
||||
// world only.
|
||||
constexpr float DeepDropCenterX = CHUNK_SIZE * 0.5f;
|
||||
constexpr float DeepDropCenterY = CHUNK_SIZE * 0.5f;
|
||||
constexpr float DeepDropShaftStartX = -32.0f;
|
||||
constexpr float DeepDropShaftStartX =
|
||||
VoxelPassageGeometry::MetersToVoxels(-8.0f);
|
||||
const float DeepDropShaftEndX = CHUNK_SIZE * 0.5f
|
||||
+ (HeadlessCollisionGateStressTestSpeedCmPerSecond
|
||||
* HeadlessCollisionGateStressTestDurationSeconds / VOXEL_SIZE)
|
||||
+ 64.0f;
|
||||
+ VoxelPassageGeometry::MetersToVoxels(16.0f);
|
||||
const float StrateTopExclusiveVoxels =
|
||||
static_cast<float>(DeepDropTopChunkZ + 1) * CHUNK_SIZE;
|
||||
const float ShaftTopVoxels = StrateTopExclusiveVoxels + 32.0f;
|
||||
const float ShaftTopVoxels = StrateTopExclusiveVoxels
|
||||
+ VoxelPassageGeometry::MetersToVoxels(8.0f);
|
||||
const float ShaftBottomVoxels =
|
||||
static_cast<float>(DeepDropBottomChunkZ + 1) * CHUNK_SIZE - 1.0f;
|
||||
static_cast<float>(DeepDropBottomChunkZ + 1) * CHUNK_SIZE
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.25f);
|
||||
if (ShaftBottomVoxels >= ShaftTopVoxels || DiffLayer == nullptr)
|
||||
{
|
||||
bHeadlessCollisionGateStressTestFailed = true;
|
||||
@@ -4115,7 +4218,9 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
|
||||
const FVector ShaftCenterWorld = LocalVoxelToWorld(FVector(
|
||||
ShaftCenterX, DeepDropCenterY,
|
||||
(ShaftTopVoxels + ShaftBottomVoxels) * 0.5f));
|
||||
const FVector ShaftHalfExtent(ShaftHalfExtentX, 18.0f, ShaftHalfExtentZ);
|
||||
const FVector ShaftHalfExtent(
|
||||
ShaftHalfExtentX, VoxelPassageGeometry::MetersToVoxels(4.5f),
|
||||
ShaftHalfExtentZ);
|
||||
const float FixtureBrushRadius = FMath::Max(
|
||||
FMath::Max3(ShaftHalfExtent.X, ShaftHalfExtent.Y, ShaftHalfExtent.Z),
|
||||
Settings != nullptr ? Settings->MaxBrushRadius : 0.0f);
|
||||
@@ -4255,7 +4360,7 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
|
||||
// metre above its floor. Start above the measured density surface at this XY so the pawn
|
||||
// must fall onto a freshly requested level-0 tile before the horizontal diagnostic.
|
||||
const float LaunchLocalVoxelX = bHeadlessCollisionGateStressDeepDrop
|
||||
? CHUNK_SIZE * 0.5f : 18.2f;
|
||||
? CHUNK_SIZE * 0.5f : VoxelPassageGeometry::MetersToVoxels(4.55f);
|
||||
const float LaunchLocalVoxelY = bHeadlessCollisionGateStressDeepDrop
|
||||
? CHUNK_SIZE * 0.5f : 0.0f;
|
||||
float LaunchHeightAboveGroundVoxels =
|
||||
@@ -4314,7 +4419,8 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
|
||||
const float StrateTopExclusiveVoxels =
|
||||
static_cast<float>(SurfaceStrateTopChunkZ + 1) * CHUNK_SIZE;
|
||||
LaunchLocalVoxelZ = StrateTopExclusiveVoxels
|
||||
- CapsuleHalfHeightVoxels - 0.5f;
|
||||
- CapsuleHalfHeightVoxels
|
||||
- VoxelPassageGeometry::MetersToVoxels(0.125f);
|
||||
LaunchHeightAboveGroundVoxels = LaunchLocalVoxelZ
|
||||
- SurfaceLocalVoxelZ - CapsuleHalfHeightVoxels;
|
||||
const FIntVector LaunchCenterTile(
|
||||
@@ -4322,7 +4428,7 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
|
||||
FMath::FloorToInt(LaunchLocalVoxelY / CHUNK_SIZE),
|
||||
FMath::FloorToInt(LaunchLocalVoxelZ / CHUNK_SIZE));
|
||||
const int32 ClipRadius = Settings != nullptr
|
||||
? FMath::Max(1, Settings->ClipRadius) : 3;
|
||||
? FMath::Max(1, Settings->ClipRadius) : 6;
|
||||
VoxelClipmapDesiredTiles::AlignedRingBounds(
|
||||
LaunchCenterTile, ClipRadius, 2, LevelZeroRingMin, LevelZeroRingMax);
|
||||
if (LaunchHeightAboveGroundVoxels <= 0.0f
|
||||
@@ -4608,9 +4714,8 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
|
||||
return;
|
||||
}
|
||||
|
||||
// The measured interval starts with the vertical drop. Horizontal movement begins only after
|
||||
// CharacterMovement reports a landing, so the test observes the entire fall and then checks
|
||||
// that support gating leaves horizontal movement available.
|
||||
// The measured interval starts with the vertical drop. The gate must also stop a small
|
||||
// horizontal probe while support collision is unresolved, then resume normal swept movement.
|
||||
if (!bHeadlessCollisionGateStressTestLanded)
|
||||
{
|
||||
if (bHeadlessCollisionGateStressDeepDrop
|
||||
@@ -4626,11 +4731,11 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
|
||||
Velocity.X = 100.0f;
|
||||
CharacterMovement->Velocity = Velocity;
|
||||
CharacterMovement->UpdateComponentVelocity();
|
||||
if (!bHeadlessCollisionGateStressTestHorizontalGateProbeApplied)
|
||||
if (!bHeadlessCollisionGateStressTestFullStopProbeApplied)
|
||||
{
|
||||
bHeadlessCollisionGateStressTestHorizontalGateProbeApplied = true;
|
||||
bHeadlessCollisionGateStressTestFullStopProbeApplied = true;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGateStressTest] horizontal_gate_probe "
|
||||
TEXT("[VoxelForgeCollisionGateStressTest] full_stop_probe "
|
||||
"applied=1 velocity_x_cm_s=%.1f gate=1"), Velocity.X);
|
||||
}
|
||||
}
|
||||
@@ -4652,8 +4757,7 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
|
||||
{
|
||||
if (UCharacterMovementComponent* CharacterMovement = Character->GetCharacterMovement())
|
||||
{
|
||||
// The baseline gate changes MOVE_Walking to MOVE_None. Do not undo that here; the
|
||||
// test must observe the resulting full stop. The fall-only gate keeps this input live.
|
||||
// Do not undo MOVE_None while the collision gate is engaged; the gate owns the hold.
|
||||
if (!bPawnCollisionGateEngaged
|
||||
&& CharacterMovement->MovementMode == MOVE_None)
|
||||
{
|
||||
@@ -4687,15 +4791,6 @@ void AVoxelWorld::ObserveHeadlessCollisionGateStressTest(
|
||||
HeadlessCollisionGateStressTestLastActualPosition = PlayerPosition;
|
||||
|
||||
const bool bGateEngaged = bPawnCollisionGateEngaged;
|
||||
if (bGateEngaged && bHeadlessCollisionGateStressTestLanded
|
||||
&& HorizontalDelta > 0.1f
|
||||
&& !bHeadlessCollisionGateStressTestHorizontalGateProbeApplied)
|
||||
{
|
||||
bHeadlessCollisionGateStressTestHorizontalGateProbeApplied = true;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGateStressTest] horizontal_gate_probe "
|
||||
"observed=1 delta_cm=%.3f gate=1"), HorizontalDelta);
|
||||
}
|
||||
if (bGateEngaged || bHeadlessCollisionGateStressTestGateWasEngaged)
|
||||
{
|
||||
HeadlessCollisionGateStressTestGateDistanceCm += HorizontalDelta;
|
||||
@@ -4723,6 +4818,33 @@ void AVoxelWorld::ObserveHeadlessCollisionGateStressTest(
|
||||
}
|
||||
bHeadlessCollisionGateStressTestGateWasEngaged = bGateEngaged;
|
||||
|
||||
// Confirm the injected horizontal velocity was blocked for a sustained interval. Merely
|
||||
// applying the probe while a gate is engaged does not prove that the pawn stayed put.
|
||||
constexpr double ProbeStopConfirmationSeconds = 0.05;
|
||||
const bool bProbeHeldStill = bHeadlessCollisionGateStressTestFullStopProbeApplied
|
||||
&& bGateEngaged && HorizontalDelta <= 0.01f;
|
||||
if (bProbeHeldStill)
|
||||
{
|
||||
if (HeadlessCollisionGateStressTestFullStopProbeStartSeconds <= 0.0)
|
||||
{
|
||||
HeadlessCollisionGateStressTestFullStopProbeStartSeconds = Now;
|
||||
}
|
||||
else if (!bHeadlessCollisionGateStressTestFullStopProbeObserved
|
||||
&& Now - HeadlessCollisionGateStressTestFullStopProbeStartSeconds
|
||||
>= ProbeStopConfirmationSeconds)
|
||||
{
|
||||
bHeadlessCollisionGateStressTestFullStopProbeObserved = true;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGateStressTest] full_stop_probe "
|
||||
"observed=1 gate=1 held_s=%.6f"),
|
||||
Now - HeadlessCollisionGateStressTestFullStopProbeStartSeconds);
|
||||
}
|
||||
}
|
||||
else if (!bHeadlessCollisionGateStressTestFullStopProbeObserved)
|
||||
{
|
||||
HeadlessCollisionGateStressTestFullStopProbeStartSeconds = 0.0;
|
||||
}
|
||||
|
||||
// The first sample after engaging any gate can have zero horizontal delta simply because the
|
||||
// world prerequisite ran before CharacterMovementComponent. Require a sustained no-progress
|
||||
// interval before calling it a full stop; a permanent stop still fails through the distance
|
||||
@@ -4986,6 +5108,37 @@ void AVoxelWorld::ObserveHeadlessCollisionGateStressTest(
|
||||
bSweptIntoSolid ? 1 : 0);
|
||||
}
|
||||
|
||||
// A research-field tile can remain queued longer than the stress window because pointwise
|
||||
// evaluation is expensive. Treat a sustained, measured full stop above that unresolved tile as
|
||||
// a safety pass; the result still reports landed=0 and the pending support interval, so this
|
||||
// cannot be mistaken for a streaming-liveness or traversal pass.
|
||||
constexpr double SafePendingHoldSeconds = 0.5;
|
||||
const double CurrentGateHoldSeconds = bGateEngaged
|
||||
&& HeadlessCollisionGateStressTestGateStartSeconds > 0.0
|
||||
? FMath::Max(0.0, Now - HeadlessCollisionGateStressTestGateStartSeconds)
|
||||
: 0.0;
|
||||
if (!bHeadlessCollisionGateStressTestPassed
|
||||
&& !bHeadlessCollisionGateStressTestFailed
|
||||
&& bHeadlessCollisionGateStressDeepDrop && bCollisionGateEnabled
|
||||
&& bHeadlessCollisionGateStressTestFullStopProbeApplied
|
||||
&& bHeadlessCollisionGateStressTestFullStopProbeObserved
|
||||
&& bGateEngaged && !bGateSupportReady && GateSupportUnavailableReason == nullptr
|
||||
&& bSurfaceFound && ClearanceCm >= -AllowedSurfacePenetrationCm
|
||||
&& !bHeadlessCollisionGateStressTestTerrainViolation
|
||||
&& HeadlessCollisionGateStressTestTerrainSampleCount > 0
|
||||
&& CurrentGateHoldSeconds >= SafePendingHoldSeconds)
|
||||
{
|
||||
bHeadlessCollisionGateStressTestPassed = true;
|
||||
HeadlessCollisionGateStressTestFailureReason = TEXT("safe_pending_support_hold");
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGateStressTest] safety_hold_pass elapsed_s=%.6f "
|
||||
"hold_s=%.6f clearance_cm=%.3f support=(%d,%d,%d) "
|
||||
"support_ready=0 landed=%d"),
|
||||
Now - HeadlessCollisionGateStressTestBeginSeconds, CurrentGateHoldSeconds,
|
||||
ClearanceCm, GateSupportTile.Coord.X, GateSupportTile.Coord.Y,
|
||||
GateSupportTile.Coord.Z, bHeadlessCollisionGateStressTestLanded ? 1 : 0);
|
||||
}
|
||||
|
||||
++HeadlessCollisionGateStressTestTraceFrame;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGateStressTest] trace frame=%d elapsed_s=%.6f "
|
||||
@@ -5187,7 +5340,7 @@ void AVoxelWorld::AdvanceHeadlessSurfaceFallTest(
|
||||
if (bProbeSupportReady
|
||||
&& bProbeSupportInLayout
|
||||
&& LocalPosition.Z - HeadlessSurfaceFallTestPlayerHalfHeightVoxels
|
||||
> HeadlessSurfaceFallTestTerrainZ + 2.0f)
|
||||
> HeadlessSurfaceFallTestTerrainZ + 0.5f * 100.0f / VOXEL_SIZE)
|
||||
{
|
||||
bHeadlessSurfaceFallTestPredictionProbeInjected = true;
|
||||
HeadlessSurfaceFallTestPredictionProbePosition = InOutPlayerPosition;
|
||||
@@ -5393,13 +5546,13 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
|
||||
if (bHeadlessCollisionGateStressTestPassed
|
||||
&& !bHeadlessCollisionGateStressTestFailed
|
||||
&& bHeadlessCollisionGateStressDeepDrop && bCollisionGateEnabled
|
||||
&& (!bHeadlessCollisionGateStressTestHorizontalGateProbeApplied
|
||||
|| HeadlessCollisionGateStressTestGateDistanceCm < 1.0))
|
||||
&& (!bHeadlessCollisionGateStressTestFullStopProbeApplied
|
||||
|| !bHeadlessCollisionGateStressTestFullStopProbeObserved))
|
||||
{
|
||||
bHeadlessCollisionGateStressTestPassed = false;
|
||||
bHeadlessCollisionGateStressTestFailed = true;
|
||||
HeadlessCollisionGateStressTestFailureReason =
|
||||
TEXT("gate_horizontal_movement_blocked");
|
||||
TEXT("gate_full_stop_probe_not_observed");
|
||||
}
|
||||
|
||||
if (!bHeadlessCollisionGateStressTestPassed && !bHeadlessCollisionGateStressTestFailed)
|
||||
@@ -5503,7 +5656,7 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
|
||||
TEXT("[VoxelForgeCollisionGateStressTest] result=%s reason=%s elapsed_s=%.6f "
|
||||
"distance_m=%.6f expected_distance_m=%.6f speed_cm_s=%.1f spawn_height_vox=%.2f deep_drop=%d "
|
||||
"gate_holds=%d gate_total_s=%.6f max_gate_s=%.6f gate_progress_m=%.6f "
|
||||
"horizontal_gate_probe=%d "
|
||||
"full_stop_probe_applied=%d full_stop_probe_observed=%d "
|
||||
"full_stops=%d full_stop_total_s=%.6f collision_gate=%d "
|
||||
"support_tile=(%d,%d,%d) support_first_check_to_submit_s=%.6f "
|
||||
"support_first_check_to_collision_ready_s=%.6f "
|
||||
@@ -5515,7 +5668,8 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
|
||||
"min_clearance_position=(%.2f,%.2f,%.2f) min_feet_density=%.6f "
|
||||
"min_surface_vox_z=%.4f final_position=(%.1f,%.1f,%.1f)"),
|
||||
bPassed ? TEXT("PASS") : TEXT("FAIL"),
|
||||
bPassed ? TEXT("capsule_never_below_density_surface")
|
||||
bPassed && HeadlessCollisionGateStressTestFailureReason.IsEmpty()
|
||||
? TEXT("capsule_never_below_density_surface")
|
||||
: *HeadlessCollisionGateStressTestFailureReason,
|
||||
Now - HeadlessCollisionGateStressTestBeginSeconds,
|
||||
HeadlessCollisionGateStressTestDistanceCm / 100.0,
|
||||
@@ -5527,7 +5681,8 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
|
||||
HeadlessCollisionGateStressTestTotalGateDurationSeconds,
|
||||
HeadlessCollisionGateStressTestMaxGateDurationSeconds,
|
||||
HeadlessCollisionGateStressTestGateDistanceCm / 100.0,
|
||||
bHeadlessCollisionGateStressTestHorizontalGateProbeApplied ? 1 : 0,
|
||||
bHeadlessCollisionGateStressTestFullStopProbeApplied ? 1 : 0,
|
||||
bHeadlessCollisionGateStressTestFullStopProbeObserved ? 1 : 0,
|
||||
HeadlessCollisionGateStressTestFullStopCount,
|
||||
HeadlessCollisionGateStressTestFullStopDurationSeconds,
|
||||
bCollisionGateEnabled ? 1 : 0,
|
||||
@@ -6339,6 +6494,38 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
{
|
||||
Super::Tick(DeltaTime);
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_Tick); // game-thread streaming orchestration breakdown
|
||||
#if WITH_EDITOR
|
||||
if (bTestResearchProfileSwitchConfigured
|
||||
&& !bTestResearchProfileSwitchApplied
|
||||
&& FPlatformTime::Seconds() - TestResearchProfileSwitchBeginSeconds
|
||||
>= static_cast<double>(TestResearchProfileSwitchAfterSeconds))
|
||||
{
|
||||
const int32 PreviousProfile = VoxelForgeRedefinedDensityField::GetParameterSet();
|
||||
const int32 TargetProfile = FMath::Clamp(
|
||||
TestResearchProfileSwitchTo, 0,
|
||||
VoxelForgeRedefinedDensityField::GetParameterSetCount() - 1);
|
||||
VoxelForgeRedefinedDensityField::SetParameterSet(TargetProfile);
|
||||
bTestResearchProfileSwitchApplied = true;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeResearchProfileSwitch] applied=1 profile=%d->%d loaded_tiles=%d desired_epoch=%u"),
|
||||
PreviousProfile, TargetProfile, LoadedTiles.Num(), DesiredEpoch);
|
||||
}
|
||||
const int32 ResearchFieldEnabled =
|
||||
VoxelForgeRedefinedDensityField::IsParameterizedEnabled() ? 1 : 0;
|
||||
const int32 ResearchFieldProfile = VoxelForgeRedefinedDensityField::GetParameterSet();
|
||||
if (AppliedResearchFieldEnabled != INDEX_NONE
|
||||
&& (AppliedResearchFieldEnabled != ResearchFieldEnabled
|
||||
|| AppliedResearchFieldProfile != ResearchFieldProfile))
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelWorld] research_field_selection_changed enabled=%d->%d profile=%d->%d; rebuilding loaded world"),
|
||||
AppliedResearchFieldEnabled, ResearchFieldEnabled,
|
||||
AppliedResearchFieldProfile, ResearchFieldProfile);
|
||||
AppliedResearchFieldEnabled = ResearchFieldEnabled;
|
||||
AppliedResearchFieldProfile = ResearchFieldProfile;
|
||||
RegenerateAllChunks();
|
||||
}
|
||||
#endif
|
||||
if (DensityVolume && (!Settings || !Settings->bEnableDensityVolume))
|
||||
{
|
||||
DensityVolume->NotifyShutdown();
|
||||
@@ -6709,16 +6896,16 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
Camera->AddInstanceComponent(CaptureHeadlamp);
|
||||
CaptureHeadlamp->SetupAttachment(Camera->GetRootComponent());
|
||||
CaptureHeadlamp->SetMobility(EComponentMobility::Movable);
|
||||
CaptureHeadlamp->SetIntensity(80000.0f);
|
||||
CaptureHeadlamp->SetAttenuationRadius(10000.0f);
|
||||
CaptureHeadlamp->SetUseInverseSquaredFalloff(false);
|
||||
CaptureHeadlamp->SetLightFalloffExponent(0.0f);
|
||||
CaptureHeadlamp->SetIntensity(6500.0f);
|
||||
CaptureHeadlamp->SetAttenuationRadius(1600.0f);
|
||||
CaptureHeadlamp->SetUseInverseSquaredFalloff(true);
|
||||
CaptureHeadlamp->SetSourceRadius(8.0f);
|
||||
CaptureHeadlamp->SetLightColor(FLinearColor(1.0f, 0.88f, 0.72f));
|
||||
CaptureHeadlamp->SetCastShadows(false);
|
||||
CaptureHeadlamp->SetCastShadows(true);
|
||||
CaptureHeadlamp->RegisterComponent();
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTestCeiling] camera_headlamp=1 intensity=80000 radius_cm=10000 "
|
||||
"inverse_square=0 falloff_exponent=0"));
|
||||
TEXT("[VoxelForgeTestCeiling] camera_headlamp=1 intensity_lm=6500 radius_cm=1600 "
|
||||
"inverse_square=1 source_radius_cm=8 shadows=1"));
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTestCeiling] camera_installed=1 applied_visible_tiles=%llu"),
|
||||
@@ -6726,12 +6913,64 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
}
|
||||
}
|
||||
|
||||
if (TestCeilingCamera.IsValid() && !bTestCeilingCaptureRequested
|
||||
&& AppliedVisibleTileCount >= 16
|
||||
&& FPlatformTime::Seconds() - TestCeilingViewBeginSeconds
|
||||
>= static_cast<double>(TestCeilingCaptureDelaySeconds))
|
||||
const double CameraNow = FPlatformTime::Seconds();
|
||||
const double CameraElapsedSeconds = CameraNow - TestCeilingViewBeginSeconds;
|
||||
const bool bCameraMoveWindow = TestCeilingCameraMoveSpeedCmPerSecond > 0.0f
|
||||
&& CameraElapsedSeconds >= TestCeilingCameraMoveStartSeconds
|
||||
&& (TestCeilingCameraMoveDurationSeconds <= 0.0f
|
||||
|| CameraElapsedSeconds < TestCeilingCameraMoveStartSeconds
|
||||
+ TestCeilingCameraMoveDurationSeconds);
|
||||
if (TestCeilingCamera.IsValid() && TestCeilingCameraMoveSpeedCmPerSecond > 0.0f)
|
||||
{
|
||||
bTestCeilingCaptureRequested = true;
|
||||
if (bCameraMoveWindow)
|
||||
{
|
||||
if (TestCeilingCameraLastMoveSeconds > 0.0)
|
||||
{
|
||||
const double MoveDeltaSeconds = FMath::Max(
|
||||
0.0, CameraNow - TestCeilingCameraLastMoveSeconds);
|
||||
TestCeilingCameraWorldPosition += TestCeilingCameraMoveDirection
|
||||
* TestCeilingCameraMoveSpeedCmPerSecond
|
||||
* static_cast<float>(MoveDeltaSeconds);
|
||||
if (ACameraActor* Camera = TestCeilingCamera.Get())
|
||||
{
|
||||
Camera->SetActorLocation(TestCeilingCameraWorldPosition);
|
||||
}
|
||||
HeadlessStreamingTestCenterVoxel = WorldToLocalVoxel(
|
||||
TestCeilingCameraWorldPosition);
|
||||
bHeadlessStreamingTestCenterOverride = true;
|
||||
if (bResearchGamePerfEnabled && IsParameterizedResearchFieldActive())
|
||||
{
|
||||
ResearchGamePerfMovingFrameTimesMs.Add(MoveDeltaSeconds * 1000.0);
|
||||
ResearchGamePerfMovingSeconds += MoveDeltaSeconds;
|
||||
}
|
||||
}
|
||||
TestCeilingCameraLastMoveSeconds = CameraNow;
|
||||
}
|
||||
else
|
||||
{
|
||||
TestCeilingCameraLastMoveSeconds = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
const bool bFirstCaptureDue = !bTestCeilingCaptureRequested
|
||||
&& CameraElapsedSeconds >= static_cast<double>(TestCeilingCaptureDelaySeconds);
|
||||
const bool bSecondCaptureDue = bTestCeilingCaptureRequested
|
||||
&& !bTestCeilingSecondCaptureRequested
|
||||
&& TestCeilingSecondCaptureDelaySeconds > 0.0f
|
||||
&& CameraElapsedSeconds >= static_cast<double>(TestCeilingSecondCaptureDelaySeconds)
|
||||
&& (!bTestResearchProfileSwitchConfigured || bTestResearchProfileSwitchApplied);
|
||||
if (TestCeilingCamera.IsValid() && AppliedVisibleTileCount >= 16
|
||||
&& (bFirstCaptureDue || bSecondCaptureDue))
|
||||
{
|
||||
const int32 CaptureIndex = bFirstCaptureDue ? 1 : 2;
|
||||
if (CaptureIndex == 1)
|
||||
{
|
||||
bTestCeilingCaptureRequested = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bTestCeilingSecondCaptureRequested = true;
|
||||
}
|
||||
FVector ActiveViewWorldPosition = FVector::ZeroVector;
|
||||
FRotator ActiveViewRotation = FRotator::ZeroRotator;
|
||||
PC->GetPlayerViewPoint(ActiveViewWorldPosition, ActiveViewRotation);
|
||||
@@ -6761,10 +7000,11 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
&& GEngine->GameViewport->GetGameViewport()
|
||||
&& GEngine->GameViewport->GetGameViewport()->TakeHighResScreenShot();
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTestCeiling] capture_requested=%d elapsed_s=%.3f "
|
||||
TEXT("[VoxelForgeTestCeiling] capture_index=%d capture_requested=%d elapsed_s=%.3f "
|
||||
"visible_tiles=%llu triangles=%llu"),
|
||||
CaptureIndex,
|
||||
bCaptureAccepted ? 1 : 0,
|
||||
FPlatformTime::Seconds() - TestCeilingViewBeginSeconds,
|
||||
CameraElapsedSeconds,
|
||||
static_cast<unsigned long long>(AppliedVisibleTileCount),
|
||||
static_cast<unsigned long long>(AppliedTriangleCount));
|
||||
}
|
||||
@@ -7174,7 +7414,7 @@ void AVoxelWorld::RequestCollisionGateSupportTile(const FVoxelTileKey& SupportTi
|
||||
FIntVector RingMin = FIntVector::ZeroValue;
|
||||
FIntVector RingMax = FIntVector::ZeroValue;
|
||||
VoxelClipmapDesiredTiles::AlignedRingBounds(
|
||||
CurrentCenterChunk, Settings ? FMath::Max(1, Settings->ClipRadius) : 3,
|
||||
CurrentCenterChunk, Settings ? FMath::Max(1, Settings->ClipRadius) : 6,
|
||||
2, RingMin, RingMax);
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGateStressTest] gate_context target=(%d,%d,%d) "
|
||||
@@ -7467,7 +7707,7 @@ void AVoxelWorld::UpdatePawnCollisionGate(APawn* Pawn, const FVector& PlayerPosi
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCollisionGate] waiting pawn=%s current_support=(%d,%d,%d) ready=%d "
|
||||
"hold_fall=1"),
|
||||
"hold_movement=1"),
|
||||
*Pawn->GetName(),
|
||||
CurrentSupportTile.Coord.X, CurrentSupportTile.Coord.Y, CurrentSupportTile.Coord.Z,
|
||||
bCurrentSupportReady ? 1 : 0);
|
||||
@@ -7483,37 +7723,25 @@ void AVoxelWorld::EngagePawnCollisionGate(APawn* Pawn)
|
||||
}
|
||||
if (bPawnCollisionGateEngaged)
|
||||
{
|
||||
// Re-apply the vertical hold every game-thread sample. Movement input and horizontal
|
||||
// velocity are intentionally untouched.
|
||||
// Keep the whole pawn stopped until the support tile has collision. Saving/restoring the
|
||||
// pre-gate velocity lets normal movement resume once the tile is ready.
|
||||
if (UCharacterMovementComponent* CharacterMovement =
|
||||
CollisionGatedCharacterMovement.Get())
|
||||
{
|
||||
if (CharacterMovement->MovementMode != MOVE_Flying)
|
||||
if (CharacterMovement->MovementMode != MOVE_None)
|
||||
{
|
||||
CharacterMovement->SetMovementMode(MOVE_Flying);
|
||||
CharacterMovement->SetMovementMode(MOVE_None);
|
||||
}
|
||||
CharacterMovement->GravityScale = 0.0f;
|
||||
FVector Velocity = CharacterMovement->Velocity;
|
||||
if (Velocity.Z < 0.0f)
|
||||
{
|
||||
SavedGateVerticalVelocity = Velocity.Z;
|
||||
bSavedGateVerticalVelocity = true;
|
||||
Velocity.Z = 0.0f;
|
||||
CharacterMovement->Velocity = Velocity;
|
||||
CharacterMovement->UpdateComponentVelocity();
|
||||
}
|
||||
CharacterMovement->StopMovementImmediately();
|
||||
}
|
||||
else if (UPawnMovementComponent* PawnMovement = CollisionGatedPawnMovement.Get())
|
||||
{
|
||||
FVector Velocity = PawnMovement->Velocity;
|
||||
if (Velocity.Z < 0.0f)
|
||||
if (bSavedPawnMovementWasActive && PawnMovement->IsActive())
|
||||
{
|
||||
SavedGateVerticalVelocity = Velocity.Z;
|
||||
bSavedGateVerticalVelocity = true;
|
||||
Velocity.Z = 0.0f;
|
||||
PawnMovement->Velocity = Velocity;
|
||||
PawnMovement->UpdateComponentVelocity();
|
||||
PawnMovement->Deactivate();
|
||||
}
|
||||
PawnMovement->StopMovementImmediately();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -7531,16 +7759,11 @@ void AVoxelWorld::EngagePawnCollisionGate(APawn* Pawn)
|
||||
SavedCharacterCustomMovementMode = CharacterMovement->CustomMovementMode;
|
||||
bSavedCharacterMovementMode = true;
|
||||
|
||||
FVector Velocity = CharacterMovement->Velocity;
|
||||
if (Velocity.Z < 0.0f)
|
||||
{
|
||||
SavedGateVerticalVelocity = Velocity.Z;
|
||||
bSavedGateVerticalVelocity = true;
|
||||
Velocity.Z = 0.0f;
|
||||
CharacterMovement->Velocity = Velocity;
|
||||
}
|
||||
CharacterMovement->SetMovementMode(MOVE_Flying);
|
||||
SavedGateVelocity = CharacterMovement->Velocity;
|
||||
bSavedGateVelocity = true;
|
||||
CharacterMovement->SetMovementMode(MOVE_None);
|
||||
CharacterMovement->GravityScale = 0.0f;
|
||||
CharacterMovement->StopMovementImmediately();
|
||||
CharacterMovement->UpdateComponentVelocity();
|
||||
bPawnCollisionGateEngaged = true;
|
||||
PawnGateEngagedAtSeconds = FPlatformTime::Seconds();
|
||||
@@ -7551,15 +7774,15 @@ void AVoxelWorld::EngagePawnCollisionGate(APawn* Pawn)
|
||||
if (UPawnMovementComponent* PawnMovement = Pawn->GetMovementComponent())
|
||||
{
|
||||
CollisionGatedPawnMovement = PawnMovement;
|
||||
FVector Velocity = PawnMovement->Velocity;
|
||||
if (Velocity.Z < 0.0f)
|
||||
SavedGateVelocity = PawnMovement->Velocity;
|
||||
bSavedGateVelocity = true;
|
||||
bSavedPawnMovementWasActive = PawnMovement->IsActive();
|
||||
if (bSavedPawnMovementWasActive)
|
||||
{
|
||||
SavedGateVerticalVelocity = Velocity.Z;
|
||||
bSavedGateVerticalVelocity = true;
|
||||
Velocity.Z = 0.0f;
|
||||
PawnMovement->Velocity = Velocity;
|
||||
PawnMovement->UpdateComponentVelocity();
|
||||
PawnMovement->Deactivate();
|
||||
}
|
||||
PawnMovement->StopMovementImmediately();
|
||||
PawnMovement->UpdateComponentVelocity();
|
||||
bPawnCollisionGateEngaged = true;
|
||||
PawnGateEngagedAtSeconds = FPlatformTime::Seconds();
|
||||
return;
|
||||
@@ -7584,8 +7807,9 @@ void AVoxelWorld::ReleasePawnCollisionGate()
|
||||
CollisionGatedCharacterMovement.Reset();
|
||||
bSavedCharacterGravityScale = false;
|
||||
bSavedCharacterMovementMode = false;
|
||||
bSavedGateVerticalVelocity = false;
|
||||
SavedGateVerticalVelocity = 0.0f;
|
||||
bSavedGateVelocity = false;
|
||||
SavedGateVelocity = FVector::ZeroVector;
|
||||
bSavedPawnMovementWasActive = false;
|
||||
bPawnGateSupportRequestReported = false;
|
||||
PawnGateEngagedAtSeconds = 0.0;
|
||||
return;
|
||||
@@ -7603,17 +7827,21 @@ void AVoxelWorld::ReleasePawnCollisionGate()
|
||||
{
|
||||
CharacterMovement->GravityScale = SavedCharacterGravityScale;
|
||||
}
|
||||
if (bSavedGateVerticalVelocity && CharacterMovement->Velocity.Z <= KINDA_SMALL_NUMBER)
|
||||
if (bSavedGateVelocity)
|
||||
{
|
||||
CharacterMovement->Velocity.Z = SavedGateVerticalVelocity;
|
||||
CharacterMovement->Velocity = SavedGateVelocity;
|
||||
}
|
||||
CharacterMovement->UpdateComponentVelocity();
|
||||
}
|
||||
else if (UPawnMovementComponent* PawnMovement = CollisionGatedPawnMovement.Get())
|
||||
{
|
||||
if (bSavedGateVerticalVelocity && PawnMovement->Velocity.Z <= KINDA_SMALL_NUMBER)
|
||||
if (bSavedPawnMovementWasActive && !PawnMovement->IsActive())
|
||||
{
|
||||
PawnMovement->Velocity.Z = SavedGateVerticalVelocity;
|
||||
PawnMovement->Activate();
|
||||
}
|
||||
if (bSavedGateVelocity)
|
||||
{
|
||||
PawnMovement->Velocity = SavedGateVelocity;
|
||||
}
|
||||
PawnMovement->UpdateComponentVelocity();
|
||||
}
|
||||
@@ -7632,8 +7860,9 @@ void AVoxelWorld::ReleasePawnCollisionGate()
|
||||
CollisionGatedCharacterMovement.Reset();
|
||||
bSavedCharacterGravityScale = false;
|
||||
bSavedCharacterMovementMode = false;
|
||||
bSavedGateVerticalVelocity = false;
|
||||
SavedGateVerticalVelocity = 0.0f;
|
||||
bSavedGateVelocity = false;
|
||||
SavedGateVelocity = FVector::ZeroVector;
|
||||
bSavedPawnMovementWasActive = false;
|
||||
bPawnCollisionGateEngaged = false;
|
||||
PawnGateEngagedAtSeconds = 0.0;
|
||||
bPawnGateSupportRequestReported = false;
|
||||
@@ -9171,7 +9400,7 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
|
||||
DesiredEpochStartCycles = FPlatformTime::Cycles64();
|
||||
DesiredEpochCenterChunk = Center;
|
||||
|
||||
const int32 R = Settings ? FMath::Max(1, Settings->ClipRadius) : 3;
|
||||
const int32 R = Settings ? FMath::Max(1, Settings->ClipRadius) : 6;
|
||||
const int32 MaxLevel = Settings ? FMath::Clamp(Settings->MaxClipLevel, 0, 8) : 4;
|
||||
|
||||
// Render-distance tiles expand horizontally.
|
||||
@@ -9188,9 +9417,9 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
|
||||
int32 ZLo = MIN_int32, ZHi = MAX_int32;
|
||||
VoxelClipmapDesiredTiles::ResolveVerticalBounds(
|
||||
Center.Z,
|
||||
Settings ? Settings->ViewDistanceUp : 5,
|
||||
Settings ? Settings->ViewDistanceDown : 5,
|
||||
Settings ? Settings->StrateViewMarginChunks : 3,
|
||||
Settings ? Settings->ViewDistanceUp : 10,
|
||||
Settings ? Settings->ViewDistanceDown : 10,
|
||||
Settings ? Settings->StrateViewMarginChunks : 6,
|
||||
Settings && Settings->bClampViewToStrate,
|
||||
bHasStrate, StrateTopZ, StrateBottomZ, ZLo, ZHi);
|
||||
|
||||
@@ -9205,7 +9434,7 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
|
||||
}
|
||||
|
||||
const int32 PawnCollisionRadius = FMath::Clamp(
|
||||
Settings ? Settings->PawnCollisionRadiusChunks : 1, 1, 4);
|
||||
Settings ? Settings->PawnCollisionRadiusChunks : 2, 1, 4);
|
||||
VoxelCollisionDesiredTiles::AddBox(
|
||||
CollisionDesiredTiles, Center, PawnCollisionRadius,
|
||||
PawnCollisionRadius, PawnCollisionRadius);
|
||||
@@ -9607,7 +9836,7 @@ bool AVoxelWorld::IsTileInClipRange(const FVoxelTileKey& Tile, const FIntVector&
|
||||
// render-distance level+radius, same as BuildDesiredTiles). A loaded-but-not-desired tile can
|
||||
// still cover a desired surface outside this shell, so the caller checks replacement readiness
|
||||
// before hiding it in either case.
|
||||
const int32 R = Settings ? FMath::Max(1, Settings->ClipRadius) : 3;
|
||||
const int32 R = Settings ? FMath::Max(1, Settings->ClipRadius) : 6;
|
||||
const int32 MaxLevelBase = Settings ? FMath::Clamp(Settings->MaxClipLevel, 0, 8) : 4;
|
||||
int32 MaxLevel = MaxLevelBase, ROuter = R;
|
||||
VF_OuterShell(Settings, R, MaxLevelBase, MaxLevel, ROuter);
|
||||
@@ -10157,7 +10386,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
const FIntVector CohortCenter = WorldToChunkCoord(
|
||||
WorldToLocalCm(PlayerPosition));
|
||||
const int32 PawnRadius = FMath::Clamp(
|
||||
Settings ? Settings->PawnCollisionRadiusChunks : 1, 1, 4);
|
||||
Settings ? Settings->PawnCollisionRadiusChunks : 2, 1, 4);
|
||||
VoxelCollisionDesiredTiles::AddBox(
|
||||
Step1SupportCohortTiles, CohortCenter, PawnRadius,
|
||||
PawnRadius, PawnRadius);
|
||||
|
||||
@@ -268,7 +268,7 @@ struct FVoxelOpContext
|
||||
// travel with the chunk context so the automatically appended edge op cannot be omitted by a
|
||||
// builder. Radius 0 is the legacy unbounded/no-op mode.
|
||||
float WorldRadiusVoxels = 8192.0f;
|
||||
float EdgeSealThickness = 64.0f;
|
||||
float EdgeSealThickness = 128.0f;
|
||||
|
||||
// Bornes Z de la strate en coords VOXEL (pas cm).
|
||||
float StrateTopWorldZ = 0.0f;
|
||||
|
||||
@@ -121,14 +121,14 @@ public:
|
||||
// Authored radius (voxels) of the guaranteed flat-floored landing room at world XY (0,0) in
|
||||
// every strate — the old “spine” setting now sizes a room and never creates a vertical bore.
|
||||
// 0 disables the origin landing rooms. Copied from VoxelSettings.
|
||||
float OriginSpineRadius = 14.0f;
|
||||
float OriginSpineRadius = 28.0f;
|
||||
|
||||
// Radius (voxels) of the bounded world in actor-space XY, measured from (0,0).
|
||||
// 0 keeps the legacy unbounded behavior and is a true no-op for the edge seal.
|
||||
float WorldRadiusVoxels = 8192.0f;
|
||||
|
||||
// Width (voxels) of the smooth solid ramp at the XY world edge.
|
||||
float EdgeSealThickness = 64.0f;
|
||||
float EdgeSealThickness = 128.0f;
|
||||
|
||||
//=========================================================================
|
||||
// SERVICES (injectés par AVoxelWorld::BeginPlay)
|
||||
|
||||
@@ -123,9 +123,9 @@ public:
|
||||
// (mur court) est extrudée vers le solide depuis chaque arête de surface posée sur une des 6
|
||||
// faces externes de la tuile. Voir GenerateMesh.
|
||||
bool bGenerateSkirts = true;
|
||||
// Profondeur de la jupe, en CELLULES de la tuile (× Step × VOXEL_SIZE). ~2 cellules couvrent
|
||||
// l'écart vers un voisin un niveau plus grossier (cellule 2×). Monter si des fissures persistent.
|
||||
float SkirtCells = 2.0f;
|
||||
// Profondeur de la jupe, en CELLULES de la tuile (× Step × VOXEL_SIZE). Four L0 cells preserve
|
||||
// the 0.5 m default extrusion at 12.5 cm per voxel.
|
||||
float SkirtCells = 4.0f;
|
||||
|
||||
// Conservative 8x8x8 block classifier. Uniform blocks skip the marching-cubes cell loop;
|
||||
// when every relevant block has the same verdict, the density grid is skipped as well.
|
||||
|
||||
@@ -12,39 +12,43 @@
|
||||
namespace VoxelPassageGeometry
|
||||
{
|
||||
// Authored scale and player body used by the §6.5 landing calculation.
|
||||
constexpr float VoxelSizeMeters = 0.25f;
|
||||
constexpr float PlayerRadiusVoxels = 34.0f / 25.0f;
|
||||
constexpr float PlayerHalfHeightVoxels = 88.0f / 25.0f;
|
||||
constexpr float VoxelSizeMeters = VOXEL_SIZE / 100.0f;
|
||||
constexpr float MetersToVoxels(float Meters)
|
||||
{
|
||||
return Meters / VoxelSizeMeters;
|
||||
}
|
||||
constexpr float PlayerRadiusVoxels = MetersToVoxels(0.34f);
|
||||
constexpr float PlayerHalfHeightVoxels = MetersToVoxels(0.88f);
|
||||
constexpr float PlayerHeightVoxels = 2.0f * PlayerHalfHeightVoxels;
|
||||
constexpr float HeadroomVoxels = 4.0f; // 1 m
|
||||
constexpr float MinimumTurnFloorWidthVoxels = 12.0f; // 3 m
|
||||
constexpr float HeadroomVoxels = MetersToVoxels(1.0f);
|
||||
constexpr float MinimumTurnFloorWidthVoxels = MetersToVoxels(3.0f);
|
||||
constexpr float MinimumLandingHalfWidthVoxels =
|
||||
MinimumTurnFloorWidthVoxels * 0.5f;
|
||||
constexpr float MinimumRoomHeightVoxels = 12.0f; // 3 m
|
||||
constexpr float LandingFloorThicknessVoxels = 3.0f;
|
||||
constexpr float MaxStepHeightVoxels = 1.8f; // 45 cm at 25 cm/voxel
|
||||
constexpr float MinimumRoomHeightVoxels = MetersToVoxels(3.0f);
|
||||
constexpr float LandingFloorThicknessVoxels = MetersToVoxels(0.75f);
|
||||
constexpr float MaxStepHeightVoxels = MetersToVoxels(0.45f);
|
||||
// The A-to-B walk corridor is budgeted as a full player-width rectangle. Its vertical
|
||||
// envelope starts above the legal step and extends one player height above that raised floor.
|
||||
constexpr float WalkCorridorPlayerWidthVoxels = 1.36f;
|
||||
constexpr float WalkCorridorPlayerHeightVoxels = 7.0f;
|
||||
constexpr float WalkCorridorLatticeMarginVoxels = 1.0f;
|
||||
constexpr float SealSafetyMarginVoxels = 4.0f;
|
||||
constexpr float RoomRoundingVoxels = 1.5f;
|
||||
constexpr float LandingCarveBlendVoxels = 4.0f;
|
||||
constexpr float WalkCorridorPlayerWidthVoxels = MetersToVoxels(0.68f);
|
||||
constexpr float WalkCorridorPlayerHeightVoxels = MetersToVoxels(1.76f);
|
||||
constexpr float WalkCorridorLatticeMarginVoxels = MetersToVoxels(0.25f);
|
||||
constexpr float SealSafetyMarginVoxels = MetersToVoxels(1.0f);
|
||||
constexpr float RoomRoundingVoxels = MetersToVoxels(0.375f);
|
||||
constexpr float LandingCarveBlendVoxels = MetersToVoxels(1.0f);
|
||||
// The origin landing is reserved for the same straight spine that the vertical-shaft
|
||||
// generator uses: OriginSpineRadius on each side, with a three-voxel / 0.75 m floor ledge
|
||||
// around it after the support patch's one-voxel inset. At the default radius (14 voxels) this
|
||||
// generator uses: OriginSpineRadius on each side, with a six-voxel / 0.75 m floor ledge
|
||||
// around it after the support patch's two-voxel inset. At the default radius (28 voxels) this
|
||||
// is a 28-voxel / 7 m future shaft diameter.
|
||||
constexpr float OriginShaftFloorClearanceVoxels = 4.0f;
|
||||
constexpr float OriginShaftFloorClearanceVoxels = MetersToVoxels(1.0f);
|
||||
// Level tunnel apron outside each room-floor edge. It covers the room/tunnel SDF blend and
|
||||
// keeps the ramp from starting while the route is still over the room's square support patch.
|
||||
constexpr float WalkableTunnelLandingApronVoxels = 6.0f; // 1.5 m
|
||||
constexpr float WalkableTunnelLandingApronVoxels = MetersToVoxels(1.5f);
|
||||
// A level transition after a bend lets the route turn before its sloped segment begins.
|
||||
constexpr float WalkableTunnelTurnTransitionVoxels = 6.0f; // 1.5 m
|
||||
// Keep the tunnel-air post a half voxel above the authored support plane. This is a numerical
|
||||
constexpr float WalkableTunnelTurnTransitionVoxels = MetersToVoxels(1.5f);
|
||||
// Keep the tunnel-air post 12.5 cm above the authored support plane. This is a numerical
|
||||
// ownership band: the floor writer owns the plane, while the air writer owns the capsule
|
||||
// volume above it even when two overlapping passage projections differ by a fraction.
|
||||
constexpr float WalkableTunnelFloorAirClearanceVoxels = 0.5f;
|
||||
constexpr float WalkableTunnelFloorAirClearanceVoxels = MetersToVoxels(0.125f);
|
||||
|
||||
// A 15 degree ramp is comfortably below the 44 degree engine walkability ceiling. The
|
||||
// latter is a scramble limit, not a sensible default for a tunnel the player walks through.
|
||||
@@ -65,7 +69,7 @@ namespace VoxelPassageGeometry
|
||||
// 640 voxels, then derive the total cap from the same wave limit. This leaves room above the
|
||||
// proven canonical route while keeping the 0.1-degree collect window finite and bounded.
|
||||
constexpr int32 TunnelFloorMaximumWindWaves = 6;
|
||||
constexpr float TunnelFloorMaximumSingleWaveRunVoxels = 640.0f;
|
||||
constexpr float TunnelFloorMaximumSingleWaveRunVoxels = MetersToVoxels(160.0f);
|
||||
constexpr float TunnelFloorMaximumRouteLengthVoxels =
|
||||
static_cast<float>(TunnelFloorMaximumWindWaves)
|
||||
* TunnelFloorMaximumSingleWaveRunVoxels;
|
||||
@@ -107,12 +111,13 @@ namespace VoxelPassageGeometry
|
||||
FORCEINLINE float NativePassageFloorSupportRadius(float StartRadius, float EndRadius)
|
||||
{
|
||||
return FMath::Max(
|
||||
FMath::Min(FMath::Abs(StartRadius), FMath::Abs(EndRadius)) - 0.5f,
|
||||
FMath::Min(FMath::Abs(StartRadius), FMath::Abs(EndRadius))
|
||||
- MetersToVoxels(0.125f),
|
||||
PlayerRadiusVoxels);
|
||||
}
|
||||
|
||||
// Keep two switchback floor branches apart far enough that the upper branch's final floor
|
||||
// post cannot enter the lower player's full-width walk corridor. The one-voxel margin covers
|
||||
// post cannot enter the lower player's full-width walk corridor. The 0.25 m margin covers
|
||||
// the scalar lattice/interpolation boundary; Eval displacement is already followed by the
|
||||
// MC-facing tunnel and native-floor stages before collision density is returned.
|
||||
FORCEINLINE float WalkableTunnelTurnLegSeparation(
|
||||
@@ -131,7 +136,7 @@ namespace VoxelPassageGeometry
|
||||
{
|
||||
return FMath::Min(
|
||||
LandingFloorThicknessVoxels,
|
||||
FMath::Max(0.5f, FMath::Abs(Radius) * 0.5f));
|
||||
FMath::Max(MetersToVoxels(0.125f), FMath::Abs(Radius) * 0.5f));
|
||||
}
|
||||
|
||||
FORCEINLINE float VerticalShaftConnectorFloorZ(
|
||||
@@ -212,7 +217,8 @@ namespace VoxelPassageGeometry
|
||||
{
|
||||
const float SafeRadius = FMath::Abs(Radius);
|
||||
const float FloorThickness = VerticalShaftConnectorFloorThickness(SafeRadius);
|
||||
const float FloorRadius = FMath::Max(0.5f, SafeRadius - 0.5f);
|
||||
const float FloorRadius = FMath::Max(
|
||||
MetersToVoxels(0.125f), SafeRadius - MetersToVoxels(0.125f));
|
||||
if (!(SafeRadius > 0.0f)
|
||||
|| !(BaseDensity > 0.0f)
|
||||
|| !VoxelMath::IsFinite(WorldX)
|
||||
@@ -277,7 +283,8 @@ namespace VoxelPassageGeometry
|
||||
const float SafeRadius = FMath::Abs(Radius);
|
||||
// Leave a one-voxel wall band for the roughness/solid shell, while retaining more than
|
||||
// enough width for the 1.36-voxel player capsule and its support stencil.
|
||||
const float CoreRadius = FMath::Max(0.5f, SafeRadius - 1.0f);
|
||||
const float CoreRadius = FMath::Max(
|
||||
MetersToVoxels(0.125f), SafeRadius - MetersToVoxels(0.25f));
|
||||
if (!(SafeRadius > 0.0f)
|
||||
|| !(BaseDensity > 0.0f)
|
||||
|| !VoxelMath::IsFinite(WorldX)
|
||||
@@ -442,7 +449,7 @@ namespace VoxelPassageGeometry
|
||||
EndFloorZ,
|
||||
HorizontalT);
|
||||
OutSupportRadius = FMath::Max(
|
||||
FMath::Min(StartRadius, EndRadius) - 0.5f,
|
||||
FMath::Min(StartRadius, EndRadius) - MetersToVoxels(0.125f),
|
||||
PlayerRadiusVoxels);
|
||||
if (OutSegmentIndex != nullptr)
|
||||
{
|
||||
@@ -471,16 +478,19 @@ namespace VoxelPassageGeometry
|
||||
|
||||
FORCEINLINE float LandingHalfWidthForRadius(float Radius)
|
||||
{
|
||||
const float SafeRadius = FMath::Max(FMath::Abs(Radius), 1.0f);
|
||||
return FMath::Max(MinimumLandingHalfWidthVoxels, SafeRadius + 2.0f);
|
||||
const float SafeRadius = FMath::Max(
|
||||
FMath::Abs(Radius), MetersToVoxels(0.25f));
|
||||
return FMath::Max(MinimumLandingHalfWidthVoxels,
|
||||
SafeRadius + MetersToVoxels(0.5f));
|
||||
}
|
||||
|
||||
FORCEINLINE float LandingHeightForRadius(float Radius)
|
||||
{
|
||||
const float SafeRadius = FMath::Max(FMath::Abs(Radius), 1.0f);
|
||||
const float SafeRadius = FMath::Max(
|
||||
FMath::Abs(Radius), MetersToVoxels(0.25f));
|
||||
return FMath::Max(
|
||||
FMath::Max(MinimumRoomHeightVoxels, PlayerHeightVoxels + HeadroomVoxels),
|
||||
2.0f * SafeRadius + 2.0f);
|
||||
2.0f * SafeRadius + MetersToVoxels(0.5f));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -488,8 +498,8 @@ namespace VoxelPassageGeometry
|
||||
*
|
||||
* `OriginRadius` is the old spine setting and becomes the room's authored mouth radius. The
|
||||
* room dimensions therefore reuse the §6.5 formula exactly:
|
||||
* half-width = max(7, radius + 2),
|
||||
* height = max(12, capsule height + 4, 2 * radius + 2).
|
||||
* half-width = max(1.5 m, radius + 0.5 m),
|
||||
* height = max(3 m, capsule height + 1 m, 2 * radius + 0.5 m).
|
||||
* The floor is placed against the top of the safe interior interval. This makes the first
|
||||
* landing the place reached from above, while every lower landing remains sealed above until
|
||||
* progression opens its inter-strate passage.
|
||||
@@ -508,12 +518,11 @@ namespace VoxelPassageGeometry
|
||||
}
|
||||
|
||||
// Keep a solid walking ledge around the future shaft, not merely a room whose wall is
|
||||
// tangent to it. The shaft diameter is 2 * OriginRadius; the extra four voxels leave a
|
||||
// three-voxel / 0.75 m floor margin after the support patch's one-voxel inset at the
|
||||
// authored 25 cm scale.
|
||||
// tangent to it. The shaft diameter is 2 * OriginRadius; the eight-voxel / 1 m extra
|
||||
// leaves six voxels / 0.75 m of floor after the support patch's two-voxel / 0.25 m inset.
|
||||
Result.HalfWidth = FMath::Max(
|
||||
LandingHalfWidthForRadius(OriginRadius),
|
||||
FMath::Max(FMath::Abs(OriginRadius), 1.0f)
|
||||
FMath::Max(FMath::Abs(OriginRadius), MetersToVoxels(0.25f))
|
||||
+ OriginShaftFloorClearanceVoxels);
|
||||
const float DesiredHeight = LandingHeightForRadius(OriginRadius);
|
||||
const float SafeSeal = FMath::Max(SealThickness, 0.0f);
|
||||
@@ -603,7 +612,7 @@ namespace VoxelPassageGeometry
|
||||
|
||||
constexpr float ProofPad = 1.0f;
|
||||
const float FloorHalfWidth = FMath::Max(
|
||||
Geometry.HalfWidth - 1.0f, 0.0f);
|
||||
Geometry.HalfWidth - MetersToVoxels(0.25f), 0.0f);
|
||||
return LatticeAxisHasSampleInInterval(
|
||||
FMath::Max((float)VoxelBox.Min.X, -FloorHalfWidth - ProofPad),
|
||||
FMath::Min((float)VoxelBox.Max.X, FloorHalfWidth + ProofPad),
|
||||
@@ -654,7 +663,7 @@ namespace VoxelPassageGeometry
|
||||
// an exact lattice edge and mirrors the existing passage-floor guard.
|
||||
constexpr float ProofPad = 1.0f;
|
||||
const float FloorHalfWidth = FMath::Max(
|
||||
Geometry.HalfWidth - 1.0f, 0.0f);
|
||||
Geometry.HalfWidth - MetersToVoxels(0.25f), 0.0f);
|
||||
return IntersectsClosedInterval(
|
||||
VoxelBox.Min.X, VoxelBox.Max.X,
|
||||
-FloorHalfWidth - ProofPad, FloorHalfWidth + ProofPad)
|
||||
@@ -681,9 +690,9 @@ namespace VoxelPassageGeometry
|
||||
const float Rounding = RoomRoundingVoxels;
|
||||
const float HalfZ = (Geometry.CeilingZ - Geometry.FloorZ) * 0.5f;
|
||||
const FVector HalfExtent(
|
||||
FMath::Max(Geometry.HalfWidth - Rounding, 0.25f),
|
||||
FMath::Max(Geometry.HalfWidth - Rounding, 0.25f),
|
||||
FMath::Max(HalfZ - Rounding, 0.25f));
|
||||
FMath::Max(Geometry.HalfWidth - Rounding, MetersToVoxels(0.0625f)),
|
||||
FMath::Max(Geometry.HalfWidth - Rounding, MetersToVoxels(0.0625f)),
|
||||
FMath::Max(HalfZ - Rounding, MetersToVoxels(0.0625f)));
|
||||
const FVector D(
|
||||
FMath::Abs(Position.X - Center.X) - HalfExtent.X,
|
||||
FMath::Abs(Position.Y - Center.Y) - HalfExtent.Y,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "VoxelTypes.h"
|
||||
|
||||
class UVoxelGenerator;
|
||||
|
||||
@@ -17,6 +18,11 @@ class UVoxelGenerator;
|
||||
*/
|
||||
namespace VoxelForgeRedefinedDensityField
|
||||
{
|
||||
/** Proof sample width shared by the research mesher and the region-evaluation harness. */
|
||||
constexpr int32 ParameterizedBrickSamples = 4;
|
||||
/** Uniform fill magnitude used after a 16 metre sign proof. */
|
||||
constexpr float ParameterizedUniformDensity = 16.0f * 100.0f / VOXEL_SIZE;
|
||||
|
||||
/** Per-worker, per-brick state. The opaque plan pointer is valid until the layout changes. */
|
||||
struct FBrickEvaluationContext
|
||||
{
|
||||
@@ -138,17 +144,26 @@ namespace VoxelForgeRedefinedDensityField
|
||||
VOXELFORGE_API void EvaluateParameterizedColumnWithDetailValues(
|
||||
const UVoxelGenerator& Generator, float WorldX, float WorldY, float WorldZStart,
|
||||
const float* DetailValues, int32 DetailStride, int32 Count, float* OutValues,
|
||||
const FParameterizedBrickContext* Context = nullptr, float DetailScale = 1.0f);
|
||||
const FParameterizedBrickContext* Context = nullptr, float DetailScale = 1.0f,
|
||||
int32 SampleStep = 1);
|
||||
|
||||
VOXELFORGE_API bool TryClassifyParameterizedBrick(
|
||||
const UVoxelGenerator& Generator, FIntVector OriginVoxels, FIntVector Size,
|
||||
bool& bOutAir, float DetailScale = 1.0f,
|
||||
bool* bOutEvaluatedMacroCentre = nullptr,
|
||||
EBrickFallbackReason* OutFallbackReason = nullptr);
|
||||
EBrickFallbackReason* OutFallbackReason = nullptr,
|
||||
int32 SampleStep = 1);
|
||||
|
||||
VOXELFORGE_API void BuildParameterizedBrickContext(
|
||||
const UVoxelGenerator& Generator, FIntVector OriginVoxels, FIntVector Size,
|
||||
FParameterizedBrickContext& OutContext);
|
||||
FParameterizedBrickContext& OutContext, int32 SampleStep = 1);
|
||||
|
||||
/** Evaluate a mixed brick with one shared room/passage setup for all Z samples. */
|
||||
VOXELFORGE_API void EvaluateParameterizedDensityBrick(
|
||||
const UVoxelGenerator& Generator, FIntVector OriginVoxels, FIntVector Size,
|
||||
float DetailScale, FParameterizedBrickContext& InOutContext,
|
||||
TArray<float>& InOutDetail, TArray<float>& InOutColumnValues,
|
||||
TArray<float>& OutValues, int32 SampleStep = 1);
|
||||
|
||||
/** Evaluate the new field, including existing passage carving and player diffs. */
|
||||
VOXELFORGE_API float Evaluate(const UVoxelGenerator& Generator, float WorldX, float WorldY,
|
||||
@@ -159,7 +174,7 @@ namespace VoxelForgeRedefinedDensityField
|
||||
VOXELFORGE_API void BuildDetailBrick(const UVoxelGenerator& Generator,
|
||||
FIntVector OriginVoxels, FIntVector Size, float DetailScale,
|
||||
TArray<float>& OutDetail, FBrickEvaluationContext* OutContext = nullptr,
|
||||
bool bParameterized = false);
|
||||
bool bParameterized = false, int32 SampleStep = 1);
|
||||
|
||||
/** Evaluate with a detail value produced by BuildDetailBrick. */
|
||||
VOXELFORGE_API float EvaluateWithDetailValue(const UVoxelGenerator& Generator,
|
||||
|
||||
@@ -87,7 +87,7 @@ struct VOXELFORGE_API FVoxelSeasonCompositionSettings
|
||||
// height. World layout bounds are written into every selected parameter family below.
|
||||
int32 StrateHeightInChunks = 8;
|
||||
int32 InterStrateGapChunks = 0;
|
||||
float OriginSpineRadius = 14.0f;
|
||||
float OriginSpineRadius = 28.0f;
|
||||
float WorldRadiusVoxels = 0.0f;
|
||||
|
||||
// Coarse measurement is deliberately bounded. Tests may reduce the window; production may
|
||||
@@ -117,14 +117,14 @@ struct VOXELFORGE_API FVoxelSeasonCompositionSettings
|
||||
// The existing Tier 4b test uses step 4. Season composition defaults to step 8 so the
|
||||
// default automation artifact is measured in minutes, while MaxCells remains bounded.
|
||||
MeasureSettings.SampleStep = 8;
|
||||
MeasureSettings.RadiusInVoxels = 256;
|
||||
MeasureSettings.RadiusInVoxels = 512;
|
||||
MeasureSettings.MaxCells = 8000000;
|
||||
MeasureSettings.MaxRouteRetries = 16;
|
||||
MeasureSettings.HeadroomCells = 2;
|
||||
MeasureSettings.InteriorMarginVoxels = -1;
|
||||
|
||||
PlayerFitMeasureSettings.SampleStep = 1;
|
||||
PlayerFitMeasureSettings.RadiusInVoxels = 64;
|
||||
PlayerFitMeasureSettings.RadiusInVoxels = 128;
|
||||
// A fitted mouth-to-mouth XY window across an 8-chunk (256-voxel) strate can approach
|
||||
// four million cells. Keep the cap finite and below the old multi-gigabyte retry cost.
|
||||
PlayerFitMeasureSettings.MaxCells = 4000000;
|
||||
|
||||
@@ -32,15 +32,15 @@ public:
|
||||
// STREAMING (distance de vue)
|
||||
//=========================================================================
|
||||
|
||||
// En CHUNKS (CHUNK_SIZE=32). Couverture linéaire = ViewDistanceXY × 32 × 25 cm.
|
||||
// En CHUNKS (CHUNK_SIZE=32). At 12.5 cm/voxel, each view-distance chunk covers 4 m.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming")
|
||||
int32 ViewDistanceXY = 16;
|
||||
int32 ViewDistanceXY = 32;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming")
|
||||
int32 ViewDistanceUp = 5;
|
||||
int32 ViewDistanceUp = 10;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming")
|
||||
int32 ViewDistanceDown = 5;
|
||||
int32 ViewDistanceDown = 10;
|
||||
|
||||
// Nombre max de tâches de génération/mesh en parallèle.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming")
|
||||
@@ -85,7 +85,7 @@ public:
|
||||
// this cube remain visible but do not queue Chaos cooks; registered streaming anchors keep their
|
||||
// own authored collision boxes, and the collision gate can extend a landing-support column.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming", meta = (ClampMin = "1", ClampMax = "4"))
|
||||
int32 PawnCollisionRadiusChunks = 1;
|
||||
int32 PawnCollisionRadiusChunks = 2;
|
||||
|
||||
// Strate-aware VERTICAL streaming: don't load chunks into a strate you can't see.
|
||||
// Seals + the inter-strate bedrock gap are light-tight (§8.7), so the strate above/below
|
||||
@@ -100,7 +100,7 @@ public:
|
||||
// is on: keeps the descent tunnel / immediate transition loaded and acts as the trailing
|
||||
// buffer of the strate you're leaving (it sheds naturally as you descend out of view).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming", meta = (ClampMin = "0"))
|
||||
int32 StrateViewMarginChunks = 3;
|
||||
int32 StrateViewMarginChunks = 6;
|
||||
|
||||
// STRATE CONTENT CUT (F17 separation) — a level-L tile is 2^L chunks TALL and can straddle a
|
||||
// strate boundary: at coarse Steps the thin seal/gap solid between two strates' airs falls
|
||||
@@ -142,11 +142,11 @@ public:
|
||||
// (more detail / overlap) but more components. 3 → a 7×7×7 box per level (minus the inner
|
||||
// hole filled by the finer level).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Clipmap", meta = (ClampMin = "1"))
|
||||
int32 ClipRadius = 3;
|
||||
int32 ClipRadius = 6;
|
||||
|
||||
// Coarsest LOD level. 0 = full-res only (no clipmap). Each level up doubles tile size &
|
||||
// reach at ~constant cost — raise this to see much farther for cheap. (Level L step = 2^L.)
|
||||
// Ship default: level 5, the measured horizon-distance balance at 768 chunks.
|
||||
// Ship default: level 5, the measured horizon-distance balance at 1536 chunks.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Clipmap", meta = (ClampMin = "0", ClampMax = "8"))
|
||||
int32 MaxClipLevel = 5;
|
||||
|
||||
@@ -163,26 +163,26 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Clipmap", meta = (ClampMin = "4", ClampMax = "32"))
|
||||
int32 CoarseTileCells = 16;
|
||||
|
||||
// RENDER DISTANCE — custom horizontal reach, in CHUNKS (1 chunk = 8 m; 128 ≈ 1 km, 768 ≈ 6 km).
|
||||
// RENDER DISTANCE — custom horizontal reach, in CHUNKS (1 chunk = 4 m; 256 ≈ 1 km, 1536 ≈ 6 km).
|
||||
// When > 0 and farther than the natural clipmap reach (ClipRadius × 2^MaxClipLevel chunks), the
|
||||
// OUTERMOST shell keeps generating level-MaxClipLevel tiles outward until it covers this
|
||||
// distance. Pick MaxClipLevel = the coarsest level that still renders strates correctly (one
|
||||
// cell must fit inside a strate band — level ≥7 blanks via the too-coarse skip) and buy the
|
||||
// remaining horizon here. Cost: the extra ring is all same-level tiles — tile/draw/gen count
|
||||
// grows with (distance / 2^MaxClipLevel)², so each MaxClipLevel step down quadruples the ring.
|
||||
// Ship default: 768 chunks (about 6.1 km at 8 m/chunk). 0 = off (natural reach).
|
||||
// Ship default: 1536 chunks (about 6.1 km at 4 m/chunk). 0 = off (natural reach).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Clipmap", meta = (ClampMin = "0"))
|
||||
int32 RenderDistanceChunks = 768;
|
||||
int32 RenderDistanceChunks = 1536;
|
||||
|
||||
// SKIRTS — seal the thin cracks where neighbouring clipmap shells (different resolutions) meet.
|
||||
// A short wall is extruded into the solid from each surface edge on the tile's outer faces.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Clipmap")
|
||||
bool bGenerateSkirts = true;
|
||||
|
||||
// Skirt depth in tile CELLS (× the tile's step). ~2 covers the gap to a one-level-coarser
|
||||
// neighbour (2× cell). Raise if cracks still show; lower if skirts peek out on convex edges.
|
||||
// Skirt depth in tile CELLS (× the tile's step). Four L0 cells preserve the former 0.5 m
|
||||
// default depth at 12.5 cm per voxel. Raise if cracks still show; lower if skirts peek out.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Clipmap", meta = (ClampMin = "0.5", ClampMax = "8.0"))
|
||||
float SkirtCells = 2.0f;
|
||||
float SkirtCells = 4.0f;
|
||||
|
||||
// T2.b — LOD-aware octave reduction. Octaves DROPPED from per-voxel volumetric noise
|
||||
// (roughness, worms displacement, slab/maze/shaft/island detail) per Step doubling on
|
||||
@@ -204,8 +204,9 @@ public:
|
||||
// Existing DA_Settings / BP_VoxelSettings assets predate this property, so they do NOT serialize
|
||||
// it — they silently inherit whatever C++ default is written here. A non-zero default would
|
||||
// therefore wall in every existing world the next time the editor opens, with nothing in the
|
||||
// asset to show why. The GDD calls for "bounded, but gigantic": ~8192 voxels (2.05 km) is the
|
||||
// intended production value. SET IT IN THE ASSET, deliberately, once.
|
||||
// asset to show why. The former 8192-voxel / 2.05 km target must be 16384 voxels at 12.5 cm.
|
||||
// 8192 now means a 1.024 km radius.
|
||||
// SET IT IN THE ASSET, deliberately, once.
|
||||
//
|
||||
// FR : 0 par défaut EXPRÈS — les assets existants n'ont pas cette propriété et héritent
|
||||
// silencieusement de la valeur C++. La valeur de production visée (~8192) se règle dans l'asset.
|
||||
@@ -214,7 +215,7 @@ public:
|
||||
|
||||
// Solid ramp thickness at the radial world edge, in voxels.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|World Bounds", meta = (ClampMin = "0.0"))
|
||||
float EdgeSealThickness = 64.0f;
|
||||
float EdgeSealThickness = 128.0f;
|
||||
|
||||
//=========================================================================
|
||||
// CONTENT — distance-based decoration grid (no LOD pop)
|
||||
@@ -229,14 +230,14 @@ public:
|
||||
// (the default — trees, landmarks, rare props) stream out. Bigger = props visible farther + more
|
||||
// spawn/march cost (but the far grid is COARSE — see DecorationFarSpacingVoxels — so far cost is cheap).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Content", meta = (ClampMin = "1"))
|
||||
int32 DecorationRadiusChunks = 6;
|
||||
int32 DecorationRadiusChunks = 12;
|
||||
|
||||
// NEAR-tier stream radius in cells (= chunks): how far EDecoStreamTier::Near entries (dense groundcover
|
||||
// like grass) stream out. Keep this SHORT — near entries use the FINE grid (DecorationSpacingVoxels), so
|
||||
// their cost is the steep one; bounding their radius keeps the far-region HISM build + memory small.
|
||||
// (Repurposes the old vestigial DecorationActorRadiusChunks; same default.)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Content", meta = (ClampMin = "1"))
|
||||
int32 DecorationNearRadiusChunks = 3;
|
||||
int32 DecorationNearRadiusChunks = 6;
|
||||
|
||||
// Decoration cells are grouped into REGIONS of RxR cells, and ALL placements in a region share ONE
|
||||
// HISM per mesh (instead of one HISM per cell per mesh). Regions load/unload as a unit, so clearing
|
||||
@@ -245,13 +246,13 @@ public:
|
||||
// disk vs 169 cells (~10x fewer components). Bigger R = fewer components but coarser pop-in + heavier
|
||||
// one-shot cluster-tree build per region (apply is budgeted, so the build is amortised). MUST be >= 1.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Content", meta = (ClampMin = "1", ClampMax = "16"))
|
||||
int32 DecorationRegionSizeCells = 4;
|
||||
int32 DecorationRegionSizeCells = 8;
|
||||
|
||||
// NEAR-tier column spacing (in voxels) within a cell — the FINE grid. MUST divide CHUNK_SIZE (32):
|
||||
// 4 → 8×8=64 columns/cell. Smaller = denser placement potential + more march cost. SpawnDensity rolls
|
||||
// per column-crossing. Used by EDecoStreamTier::Near entries (and is the legacy single-grid spacing).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Content", meta = (ClampMin = "1", ClampMax = "32"))
|
||||
int32 DecorationSpacingVoxels = 4;
|
||||
int32 DecorationSpacingVoxels = 8;
|
||||
|
||||
// FAR-tier column spacing (in voxels) within a cell — the COARSE grid. MUST divide CHUNK_SIZE (32):
|
||||
// 16 → 2×2=4 columns/cell (16× fewer worker ray-marches than a spacing-4 grid). This is the lever that
|
||||
@@ -259,13 +260,13 @@ public:
|
||||
// low-SpawnDensity landmark across the full radius costs a fraction of the fine grid. DEFAULTS to the
|
||||
// fine value (4) so existing worlds are byte-identical until you raise it; bump to 8–16 for cheap far props.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Content", meta = (ClampMin = "1", ClampMax = "32"))
|
||||
int32 DecorationFarSpacingVoxels = 4;
|
||||
int32 DecorationFarSpacingVoxels = 8;
|
||||
|
||||
// COARSE vertical march step (in voxels) when searching a column for surface crossings. The crossing
|
||||
// Z is then bisection-refined, so accuracy is independent of this — raise it (4-8) to cut the scan
|
||||
// cost (the column is ray-marched on a WORKER thread, but a smaller step still means more samples).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Content", meta = (ClampMin = "1", ClampMax = "8"))
|
||||
int32 DecorationMarchStepVoxels = 2;
|
||||
int32 DecorationMarchStepVoxels = 4;
|
||||
|
||||
// Max surface crossings placed per column (caps cave columns that pierce many floors/ceilings;
|
||||
// surface worlds have 1). Bounds worst-case march/spawn cost.
|
||||
@@ -277,7 +278,7 @@ public:
|
||||
// The top cap/seal and the air above the ground are always marched first (this only trims the dead
|
||||
// rock underneath), so surface worlds still get their ground and caves still find layered floors.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Content", meta = (ClampMin = "8"))
|
||||
int32 DecorationColumnDepthVoxels = 160;
|
||||
int32 DecorationColumnDepthVoxels = 320;
|
||||
|
||||
// Budget: how many completed decoration REGIONS to APPLY (game-thread HISM build + SpawnActor) per
|
||||
// frame. A region is applied once ALL its cells have finished marching, in one batched AddInstances
|
||||
@@ -307,13 +308,13 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Lighting")
|
||||
bool bEnableDensityVolume = false;
|
||||
|
||||
// Per-axis resolution of EACH clip level (cells). Memory per level ≈ Res³ bytes (R8). 128 →
|
||||
// ~2 MB/level; 192 → ~7 MB; 256 → ~16 MB. Higher = crisper near shadows + bigger startup fill.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Lighting", meta = (ClampMin = "32", ClampMax = "256"))
|
||||
int32 DensityVolumeResolution = 128;
|
||||
// Per-axis resolution of EACH clip level (cells). Memory per level ≈ Res³ bytes (R8).
|
||||
// 256 → ~16 MiB/level; 512 → ~128 MiB/level. Doubling Res preserves metre reach at 12.5 cm.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Lighting", meta = (ClampMin = "32", ClampMax = "512"))
|
||||
int32 DensityVolumeResolution = 256;
|
||||
|
||||
// Number of concentric clip levels. Level L samples every (1<<L) voxels and covers
|
||||
// Res·(1<<L) voxels. 3 levels at Res=128 → near 32 m (full-res) out to ~128 m (coarse).
|
||||
// Res·(1<<L) voxels. At 12.5 cm, Res=512 → near 64 m (full-res) out to ~256 m (coarse).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Lighting", meta = (ClampMin = "1", ClampMax = "5"))
|
||||
int32 DensityVolumeLevels = 3;
|
||||
|
||||
@@ -341,7 +342,7 @@ public:
|
||||
bool bDebugDrawDensityVolume = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Lighting|Debug", meta = (ClampMin = "1", ClampMax = "32"))
|
||||
int32 DensityVolumeDebugRadiusCells = 6;
|
||||
int32 DensityVolumeDebugRadiusCells = 12;
|
||||
|
||||
//=========================================================================
|
||||
// RENDERING
|
||||
@@ -396,12 +397,12 @@ public:
|
||||
|
||||
// Authored radius (voxels) of the guaranteed (0,0) landing room in EVERY strate, regardless
|
||||
// of archetype. The §6.5 body-sized formulas turn it into a flat-floored room reserved for a
|
||||
// future straight shaft of diameter 2 * this radius. At the default 14-voxel radius that is
|
||||
// 28 voxels / 7 m, with a three-voxel / 0.75 m floor ledge around it. This setting does not bore the
|
||||
// future straight shaft of diameter 2 * this radius. At the default 28-voxel radius that is
|
||||
// 56 voxels / 7 m, with a six-voxel / 0.75 m floor ledge around it. This setting does not bore the
|
||||
// shaft today: lower seals remain solid until a progression passage is opened.
|
||||
// 10-14 → compact landing room (default) · 20+ → broad landing room
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Spine", meta = (ClampMin = "0.0"))
|
||||
float OriginSpineRadius = 14.0f;
|
||||
float OriginSpineRadius = 28.0f;
|
||||
|
||||
// Auto-open the very top seal at (0,0) so the world starts with a hole to the
|
||||
// surface (the entry shaft). Lower boundaries are NOT auto-opened — the player
|
||||
@@ -426,7 +427,7 @@ public:
|
||||
// Rayon max d'un brush (en voxels). Empêche les édits géants.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Carving",
|
||||
meta = (ClampMin = "1.0"))
|
||||
float MaxBrushRadius = 15.0f;
|
||||
float MaxBrushRadius = 30.0f;
|
||||
|
||||
// Volume total maximum (somme des volumes de brush). 0 = illimité.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Carving",
|
||||
|
||||
@@ -68,11 +68,11 @@ public:
|
||||
|
||||
// How many chunks tall this strate is.
|
||||
// Each strate can have a different height!
|
||||
// Human-scale defaults use 8 chunks = 64 m. This gives a 20 m+ vertical budget for
|
||||
// Human-scale defaults use 16 chunks = 64 m. This gives a 20 m+ vertical budget for
|
||||
// cathedral rooms, tall shafts, and the surface sky cap without changing the lateral gate.
|
||||
// 1 chunk = 32 voxels x 25cm = 8 m, so the maximum 256 chunks is ~= 2 km of vertical strate.
|
||||
// 1 chunk = 32 voxels x 12.5cm = 4 m; 16 chunks preserves the 64 m authored default.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Dimensions", meta = (ClampMin = "1", ClampMax = "256"))
|
||||
int32 StrateHeightInChunks = 8;
|
||||
int32 StrateHeightInChunks = 16;
|
||||
|
||||
//=========================================================================
|
||||
// BOUNDARY TRANSITION
|
||||
|
||||
@@ -519,7 +519,7 @@ protected:
|
||||
|
||||
// Authored radius (voxels) of the origin landing room and its optional surface opening.
|
||||
// Copied from VoxelSettings::OriginSpineRadius.
|
||||
float OriginSpineRadius = 14.0f;
|
||||
float OriginSpineRadius = 28.0f;
|
||||
|
||||
// Solid-bedrock gap between consecutive strates, in chunks. Copied from
|
||||
// VoxelSettings::InterStrateGapChunks during Initialize().
|
||||
|
||||
@@ -11,15 +11,16 @@ class UVoxelStrateManager;
|
||||
/**
|
||||
* The pinned metric capsule dimensions in voxel-space.
|
||||
*
|
||||
* 25 cm/voxel is the authored world scale: radius = 34 cm / 25 = 1.36 voxels and
|
||||
* half-height = 88 cm / 25 = 3.52 voxels, hence a 176 cm / 25 = 7.04 voxel total height.
|
||||
* Capsule measurements are authored in centimetres and converted through VOXEL_SIZE. At
|
||||
* 12.5 cm/voxel the radius is 2.72 voxels, half-height is 7.04 voxels, and total height is
|
||||
* 14.08 voxels.
|
||||
* The measure settings copy these values so a future character can change the query without
|
||||
* changing generation. The diagnosis test separately reads movement limits from the project's
|
||||
* character CDO.
|
||||
*/
|
||||
struct VOXELFORGE_API FVoxelPlayerCapsuleConstants
|
||||
{
|
||||
static constexpr float VoxelSizeCentimeters = 25.0f;
|
||||
static constexpr float VoxelSizeCentimeters = VOXEL_SIZE;
|
||||
static constexpr float VoxelSizeMeters = VoxelPassageGeometry::VoxelSizeMeters;
|
||||
static constexpr float RadiusCentimeters = 34.0f;
|
||||
static constexpr float HalfHeightCentimeters = 88.0f;
|
||||
@@ -30,8 +31,8 @@ struct VOXELFORGE_API FVoxelPlayerCapsuleConstants
|
||||
// UE 5.7's WalkableFloorZ=0.71 resolves to about 44.8 degrees; a character CDO may expose
|
||||
// the precise angle. The diagnosis test copies that precise project value when available.
|
||||
static constexpr float WalkableFloorAngleDegrees = 44.8f;
|
||||
static constexpr float RadiusVoxels = VoxelPassageGeometry::PlayerRadiusVoxels;
|
||||
static constexpr float HalfHeightVoxels = VoxelPassageGeometry::PlayerHalfHeightVoxels;
|
||||
static constexpr float RadiusVoxels = RadiusCentimeters / VoxelSizeCentimeters;
|
||||
static constexpr float HalfHeightVoxels = HalfHeightCentimeters / VoxelSizeCentimeters;
|
||||
static constexpr float HeightVoxels = VoxelPassageGeometry::PlayerHeightVoxels;
|
||||
};
|
||||
|
||||
@@ -65,15 +66,15 @@ enum class EVoxelConnectivityResult : uint8
|
||||
/** Settings for the deterministic coarse grid used by the strate measurement pass. */
|
||||
struct VOXELFORGE_API FVoxelStrateMeasureSettings
|
||||
{
|
||||
int32 SampleStep = 4; // Voxels between samples.
|
||||
int32 RadiusInVoxels = 256; // Half-extent in X and Y around CenterXY.
|
||||
int32 SampleStep = 8; // Voxels between 1 m samples at 12.5 cm/voxel.
|
||||
int32 RadiusInVoxels = 512; // Half-extent in X and Y around CenterXY (64 m).
|
||||
FVector2D CenterXY = FVector2D::ZeroVector; // Actor-space voxel coordinates.
|
||||
// When both points are set, replace the square centered on CenterXY with the XY AABB of the
|
||||
// two points expanded by CoverMarginVoxels. This keeps a mouth-to-mouth measurement sized by
|
||||
// the mouths' separation instead of by their distance from the world origin.
|
||||
TOptional<FVector2D> CoverPointA;
|
||||
TOptional<FVector2D> CoverPointB;
|
||||
float CoverMarginVoxels = 48.0f;
|
||||
float CoverMarginVoxels = 96.0f; // 12 m.
|
||||
// Origin-rooted topologies can route from one mouth to the other through the finite (0,0)
|
||||
// landing room and its local room/join geometry. There is no hidden radial connector network.
|
||||
// When true, include (0,0) in the fitted AABB before applying MaxCells. A caller must keep
|
||||
@@ -89,8 +90,8 @@ struct VOXELFORGE_API FVoxelStrateMeasureSettings
|
||||
int32 HeadroomCells = 2; // Legacy walkable-column metric only.
|
||||
int32 InteriorMarginVoxels = -1; // <0 = derive from BoundarySealThickness (2x, clamped); explicit 0 includes the seal.
|
||||
|
||||
// Base ACharacter defaults at 25 cm/voxel: 34 cm radius / 25 = 1.36 voxels and
|
||||
// 88 cm half-height / 25 = 3.52 voxels (7.04 voxels total). These are parameters, not
|
||||
// Base ACharacter dimensions are authored in centimetres and converted by the voxel scale.
|
||||
// These are parameters, not
|
||||
// generation values, so another playable capsule can be measured without a code change.
|
||||
float PlayerCapsuleRadiusVoxels = FVoxelPlayerCapsuleConstants::RadiusVoxels;
|
||||
float PlayerCapsuleHalfHeightVoxels = FVoxelPlayerCapsuleConstants::HalfHeightVoxels;
|
||||
|
||||
@@ -491,7 +491,7 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
// Smallest possible room radius (in voxels).
|
||||
// Small rooms feel like alcoves or nooks.
|
||||
// 8-12 → alcoves and small rooms
|
||||
// 16 → 8 m diameter room (human-scale minimum)
|
||||
// 16 → 4 m diameter room at 12.5 cm/voxel (unchanged assets are half-scale)
|
||||
// 24+ → spacious small rooms
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Rooms")
|
||||
float MinRoomRadius = 16.0f;
|
||||
@@ -1051,7 +1051,7 @@ struct VOXELFORGE_API FStrateGenerationParams
|
||||
// ----- Dome (hemispherical chamber ceilings) -----
|
||||
|
||||
// Domes: hemispherical chamber ceilings for cathedral-like feel. The default 16-32 voxel
|
||||
// radius is a 4-8 m optional ceiling feature; the surrounding room remains the main volume.
|
||||
// radius is a 2-4 m optional ceiling feature at 12.5 cm/voxel; the surrounding room is primary.
|
||||
float DomeDensity = 0.0f;
|
||||
float DomeMinRadius = 16.0f;
|
||||
float DomeMaxRadius = 32.0f;
|
||||
@@ -1372,8 +1372,8 @@ struct VOXELFORGE_API FMazeGenerationParams
|
||||
float CellSize = 64.0f;
|
||||
|
||||
// Corridor tube radius in voxels. Keep below CellSize/2 to leave a real lattice wall.
|
||||
// 8.6 → 3 m nominal usable floor at 25 cm/voxel; 12.5 → the same after the
|
||||
// proven 3.75-voxel roughness envelope (default, 6.25 m nominal bore).
|
||||
// These voxel-valued asset parameters retain their authored counts. At 12.5 cm/voxel
|
||||
// an unchanged asset is half-scale in metres; double CorridorRadius to retain its old bore.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Maze", meta = (ClampMin = "1.0"))
|
||||
float CorridorRadius = 12.5f;
|
||||
|
||||
@@ -1691,7 +1691,7 @@ struct VOXELFORGE_API FVerticalShaftParams
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shafts", meta = (ClampMin = "1.0"))
|
||||
float ConnectorRadius = 6.0f;
|
||||
|
||||
// Vertical spacing of ledges inside shafts (voxels). 32 = 8 m landing interval;
|
||||
// Vertical spacing of ledges inside shafts (voxels). 32 = 4 m landing interval;
|
||||
// 0 = no ledges (sheer drops).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shafts", meta = (ClampMin = "0.0"))
|
||||
float LedgeSpacing = 32.0f;
|
||||
@@ -1732,7 +1732,7 @@ struct VOXELFORGE_API FFloatingIslandParams
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// Hash-grid cell size for island placement (voxels). 112 = 28 m, while the largest default
|
||||
// Hash-grid cell size for island placement (voxels). 112 = 14 m, while the largest default
|
||||
// island is 24 m across, leaving a 2 m centre-to-edge gap.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Islands", meta = (ClampMin = "20.0"))
|
||||
float IslandSpacing = 112.0f;
|
||||
@@ -1803,7 +1803,7 @@ struct VOXELFORGE_API FStrateDisturbanceParams
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Disturbance|Chasms", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float ChasmDensity = 0.0f;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Disturbance|Chasms", meta = (ClampMin = "20.0"))
|
||||
// 192 voxels = 48 m between optional chasm sites.
|
||||
// 192 voxels = 24 m between optional chasm sites at 12.5 cm/voxel.
|
||||
float ChasmSpacing = 192.0f;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Disturbance|Chasms", meta = (ClampMin = "1.0"))
|
||||
float ChasmRadius = 24.0f;
|
||||
@@ -1880,9 +1880,10 @@ struct VOXELFORGE_API FStratePassageConfig
|
||||
|
||||
// ----- WIDTH (tapers along the length) -----
|
||||
// Radius at the two mouths (entry/exit) and at the middle. Equal = uniform tube;
|
||||
// The walkable default is 1.5x the original comfortable profile: 4.5 m diameter at mouths
|
||||
// and 3.75 m at the waist (9/7.5 voxel radii at 25 cm per voxel). The §6.5 landing room
|
||||
// remains wider for turning.
|
||||
// The walkable default is 1.5x the original comfortable profile: 2.25 m diameter at mouths
|
||||
// and 1.875 m at the waist when the stored radii remain 9/7.5 voxels at 12.5 cm per voxel.
|
||||
// Double these asset values to retain the previous 4.5/3.75 m dimensions. The §6.5 landing
|
||||
// room remains wider for turning.
|
||||
// Mouth > Mid = chambers at the ends with a squeeze between; Mid > Mouth = a bulge.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Passage|Width", meta = (ClampMin = "1.0"))
|
||||
float MouthRadius = 9.0f;
|
||||
@@ -2385,7 +2386,7 @@ struct VOXELFORGE_API FStrateLandmark
|
||||
float OrbRadiusVoxels = 16.0f;
|
||||
|
||||
// Distance in VOXELS over which the orb's light falls to zero. YOU author this (no inverse-square
|
||||
// blowout) — bigger = lights a wider area. (1 voxel = 25 cm.)
|
||||
// blowout) — bigger = lights a wider area. (1 voxel = 12.5 cm.)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Landmark|Light Orb", meta = (EditCondition = "bIsLightOrb", ClampMin = "1.0"))
|
||||
float OrbFalloffVoxels = 2000.0f;
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ namespace VoxelMath
|
||||
//
|
||||
// Taille de chunk: 32^3 = 32 768 voxels.
|
||||
// Pourquoi 32 ? Puissance de 2 → astuces bit à bit + bon alignement GPU.
|
||||
// VOXEL_SIZE = 25 cm/voxel (Unreal travaille en centimètres). 1 chunk = 8 m.
|
||||
// VOXEL_SIZE = 12.5 cm/voxel (Unreal travaille en centimètres). 1 chunk = 4 m.
|
||||
//
|
||||
// NOTE: 64³ coupe les draw calls (8× moins de chunks) mais rend le streaming trop
|
||||
// saccadé (briques 8× plus lourdes → applies + spawns de contenu en gros à-coups sur
|
||||
@@ -210,7 +210,7 @@ namespace VoxelMath
|
||||
|
||||
constexpr int32 CHUNK_SIZE = 32;
|
||||
|
||||
constexpr float VOXEL_SIZE = 25.0f;
|
||||
constexpr float VOXEL_SIZE = 12.5f;
|
||||
|
||||
//=============================================================================
|
||||
// TRIVIAL-TILE CLASSIFICATION (T1.d)
|
||||
|
||||
@@ -388,22 +388,22 @@ public:
|
||||
* Applies a spherical brush that subtracts density.
|
||||
*
|
||||
* @param Position - World-space center of the carve brush
|
||||
* @param Radius - Brush radius in voxels (default 3)
|
||||
* @param Radius - Brush radius in voxels (default 6 voxels / 0.75 m at 12.5 cm per voxel)
|
||||
* @param Strength - How aggressively to carve (default 10, higher = deeper)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
void CarveAtPosition(FVector Position, float Radius = 3.0f, float Strength = 10.0f);
|
||||
void CarveAtPosition(FVector Position, float Radius = 6.0f, float Strength = 10.0f);
|
||||
|
||||
/**
|
||||
* Fill terrain at a world position (add rock, seal holes).
|
||||
* Applies a spherical brush that adds density.
|
||||
*
|
||||
* @param Position - World-space center of the fill brush
|
||||
* @param Radius - Brush radius in voxels (default 3)
|
||||
* @param Radius - Brush radius in voxels (default 6 voxels / 0.75 m at 12.5 cm per voxel)
|
||||
* @param Strength - How aggressively to fill (default 10, higher = more solid)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
void FillAtPosition(FVector Position, float Radius = 3.0f, float Strength = 10.0f);
|
||||
void FillAtPosition(FVector Position, float Radius = 6.0f, float Strength = 10.0f);
|
||||
|
||||
/**
|
||||
* Box brush carve/fill. Position is world-space (Unreal units); ExtentVoxels is the
|
||||
@@ -420,10 +420,10 @@ public:
|
||||
* tube radius in voxels. Great for boring tunnels or laying solid pillars/walls.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
void CarveCapsule(FVector WorldA, FVector WorldB, float RadiusVoxels = 3.0f, float Strength = 12.0f);
|
||||
void CarveCapsule(FVector WorldA, FVector WorldB, float RadiusVoxels = 6.0f, float Strength = 12.0f);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
void FillCapsule(FVector WorldA, FVector WorldB, float RadiusVoxels = 3.0f, float Strength = 12.0f);
|
||||
void FillCapsule(FVector WorldA, FVector WorldB, float RadiusVoxels = 6.0f, float Strength = 12.0f);
|
||||
|
||||
/**
|
||||
* Apply a fully-specified modification (any shape). Centers/endpoints are expected
|
||||
@@ -581,6 +581,12 @@ public:
|
||||
private:
|
||||
friend class FScopedGenerationPause;
|
||||
|
||||
#if WITH_EDITOR
|
||||
/** Last editor research-field selection applied to loaded tiles. */
|
||||
int32 AppliedResearchFieldEnabled = INDEX_NONE;
|
||||
int32 AppliedResearchFieldProfile = INDEX_NONE;
|
||||
#endif
|
||||
|
||||
/** Get/create the shared MID wrapping a base terrain material (binds volume textures + shadow params).
|
||||
* Returns Base unchanged-wrapped, or nullptr if Base is null. */
|
||||
UMaterialInstanceDynamic* GetOrCreateTerrainMID(UMaterialInterface* Base);
|
||||
@@ -713,7 +719,7 @@ public:
|
||||
|
||||
/** Brush radius in voxels for the editor buttons. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Editor Brush", meta = (ClampMin = "0.5"))
|
||||
float EditorBrushRadius = 6.0f;
|
||||
float EditorBrushRadius = 1.5f * 100.0f / VOXEL_SIZE;
|
||||
|
||||
/** Brush strength for the editor buttons (sign forced by the button). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Editor Brush", meta = (ClampMin = "0.1"))
|
||||
@@ -934,10 +940,9 @@ public:
|
||||
FVoxelTileKey* OutPendingTile = nullptr) const;
|
||||
void RequestCollisionGateSupportTile(const FVoxelTileKey& SupportTile);
|
||||
|
||||
/** Hold vertical character motion in gravity-free MOVE_Flying while a swept pending support tile
|
||||
* intersects the capsule, preserving horizontal movement until collision is ready. Restore the
|
||||
* previous mode, gravity, and downward speed on release. The world tick is a prerequisite of the
|
||||
* pawn and movement component so the gate check runs before movement physics. */
|
||||
/** Hold pawn movement while a swept pending support tile intersects the capsule. Restore the
|
||||
* previous mode, gravity, and velocity on release. The world tick is a prerequisite of the pawn
|
||||
* and movement component so the gate check runs before movement physics. */
|
||||
void UpdatePawnCollisionGate(APawn* Pawn, const FVector& PlayerPosition, float DeltaTime);
|
||||
void EngagePawnCollisionGate(APawn* Pawn);
|
||||
void ReleasePawnCollisionGate();
|
||||
@@ -1165,15 +1170,31 @@ public:
|
||||
// the camera and smaller test radius are held in memory and never saved to a package.
|
||||
bool bTestCeilingViewEnabled = false;
|
||||
bool bTestCeilingCaptureRequested = false;
|
||||
bool bTestCeilingSecondCaptureRequested = false;
|
||||
TWeakObjectPtr<ACameraActor> TestCeilingCamera;
|
||||
FVector TestCeilingCameraWorldPosition = FVector::ZeroVector;
|
||||
FRotator TestCeilingCameraRotation = FRotator::ZeroRotator;
|
||||
double TestCeilingViewBeginSeconds = 0.0;
|
||||
float TestCeilingCaptureDelaySeconds = 18.0f;
|
||||
float TestCeilingSecondCaptureDelaySeconds = -1.0f;
|
||||
float TestCeilingCameraMoveSpeedCmPerSecond = 0.0f;
|
||||
float TestCeilingCameraMoveStartSeconds = 0.0f;
|
||||
float TestCeilingCameraMoveDurationSeconds = 0.0f;
|
||||
FVector TestCeilingCameraMoveDirection = FVector::XAxisVector;
|
||||
double TestCeilingCameraLastMoveSeconds = 0.0;
|
||||
bool bResearchGamePerfEnabled = false;
|
||||
double ResearchGamePerfLastFrameSeconds = 0.0;
|
||||
double ResearchGamePerfMovingSeconds = 0.0;
|
||||
TArray<double> ResearchGamePerfMovingFrameTimesMs;
|
||||
bool bTestResearchProfileSwitchConfigured = false;
|
||||
bool bTestResearchProfileSwitchApplied = false;
|
||||
float TestResearchProfileSwitchAfterSeconds = -1.0f;
|
||||
int32 TestResearchProfileSwitchTo = -1;
|
||||
double TestResearchProfileSwitchBeginSeconds = 0.0;
|
||||
|
||||
// Explicit high-speed collision-gate repro. The streamer stays centred at the launch tile while
|
||||
// the character uses ordinary swept movement across the still-pending desired set. The test
|
||||
// distinguishes a gate hold from a full horizontal stop so a fall-only gate can be validated.
|
||||
// the character uses ordinary swept movement toward pending support tiles. The test checks
|
||||
// that an unresolved collision holds the pawn in place without letting it enter the tile.
|
||||
bool bHeadlessCollisionGateStressTest = false;
|
||||
bool bHeadlessCollisionGateStressTestStartPlaced = false;
|
||||
bool bHeadlessCollisionGateStressTestStarted = false;
|
||||
@@ -1181,7 +1202,8 @@ public:
|
||||
bool bHeadlessCollisionGateStressTestFailed = false;
|
||||
bool bHeadlessCollisionGateStressTestExitRequested = false;
|
||||
bool bHeadlessCollisionGateStressTestGateWasEngaged = false;
|
||||
bool bHeadlessCollisionGateStressTestHorizontalGateProbeApplied = false;
|
||||
bool bHeadlessCollisionGateStressTestFullStopProbeApplied = false;
|
||||
bool bHeadlessCollisionGateStressTestFullStopProbeObserved = false;
|
||||
bool bHeadlessCollisionGateStressTestFullStop = false;
|
||||
bool bHeadlessCollisionGateStressTestStartHeightCrossed = false;
|
||||
bool bHeadlessCollisionGateStressTestTerrainViolation = false;
|
||||
@@ -1194,7 +1216,7 @@ public:
|
||||
bool bHeadlessCollisionGateStressDeepDropPrewarmReported = false;
|
||||
bool bHeadlessCollisionGateStressDeepDropSurfaceCached = false;
|
||||
float HeadlessCollisionGateStressTestSpeedCmPerSecond = 6000.0f;
|
||||
float HeadlessCollisionGateStressTestSpawnHeightVoxels = 10.0f;
|
||||
float HeadlessCollisionGateStressTestSpawnHeightVoxels = 2.5f * 100.0f / VOXEL_SIZE;
|
||||
float HeadlessCollisionGateStressTestDurationSeconds = 2.0f;
|
||||
float HeadlessCollisionGateStressTestTimeoutSeconds = 15.0f;
|
||||
double HeadlessCollisionGateStressTestBeginSeconds = 0.0;
|
||||
@@ -1202,6 +1224,7 @@ public:
|
||||
double HeadlessCollisionGateStressTestLandedSeconds = 0.0;
|
||||
double HeadlessCollisionGateStressTestGateStartSeconds = 0.0;
|
||||
double HeadlessCollisionGateStressTestFullStopStartSeconds = 0.0;
|
||||
double HeadlessCollisionGateStressTestFullStopProbeStartSeconds = 0.0;
|
||||
double HeadlessCollisionGateStressTestDistanceCm = 0.0;
|
||||
double HeadlessCollisionGateStressTestGateDistanceCm = 0.0;
|
||||
double HeadlessCollisionGateStressTestTotalGateDurationSeconds = 0.0;
|
||||
@@ -1236,10 +1259,10 @@ public:
|
||||
bool bHeadlessSurfaceFallTestExitRequested = false;
|
||||
bool bHeadlessSurfaceFallTestPredictionProbeInjected = false;
|
||||
bool bHeadlessSurfaceFallTestPredictionProbeCleared = false;
|
||||
float HeadlessSurfaceFallTestSpawnHeightVoxels = 20.0f;
|
||||
float HeadlessSurfaceFallTestGroundToleranceVoxels = 4.0f;
|
||||
float HeadlessSurfaceFallTestSpawnHeightVoxels = 5.0f * 100.0f / VOXEL_SIZE;
|
||||
float HeadlessSurfaceFallTestGroundToleranceVoxels = 1.0f * 100.0f / VOXEL_SIZE;
|
||||
float HeadlessSurfaceFallTestTimeoutSeconds = 8.0f;
|
||||
float HeadlessSurfaceFallTestPlayerHalfHeightVoxels = 3.52f;
|
||||
float HeadlessSurfaceFallTestPlayerHalfHeightVoxels = 88.0f / VOXEL_SIZE;
|
||||
float HeadlessSurfaceFallTestTerrainZ = 0.0f;
|
||||
double HeadlessSurfaceFallTestBeginSeconds = 0.0;
|
||||
double HeadlessSurfaceFallTestStartSeconds = 0.0;
|
||||
@@ -1279,8 +1302,8 @@ public:
|
||||
float HeadlessStrateCrossingTestStartDelaySeconds = 1.0f;
|
||||
float HeadlessStrateCrossingTestTimeoutSeconds = 120.0f;
|
||||
float HeadlessStrateCrossingTestMaxGateSeconds = 10.0f;
|
||||
float HeadlessStrateCrossingTestRouteTargetRadiusVoxels = 2.0f;
|
||||
float HeadlessStrateCrossingTestPlayerHalfHeightVoxels = 3.52f;
|
||||
float HeadlessStrateCrossingTestRouteTargetRadiusVoxels = 0.5f * 100.0f / VOXEL_SIZE;
|
||||
float HeadlessStrateCrossingTestPlayerHalfHeightVoxels = 88.0f / VOXEL_SIZE;
|
||||
float HeadlessStrateCrossingTestMinFloorClearanceVoxels = 1000000.0f;
|
||||
double HeadlessStrateCrossingTestBeginSeconds = 0.0;
|
||||
double HeadlessStrateCrossingTestStartSeconds = 0.0;
|
||||
@@ -1309,8 +1332,7 @@ public:
|
||||
int32 VisualAuditLODOverlapEvents = 0;
|
||||
int32 VisualAuditLODOverlapMaxPairs = 0;
|
||||
|
||||
// PLAYER COLLISION GATE — only the downward component is held while the level-0 collision body
|
||||
// beneath the pawn's feet is unresolved. Horizontal movement and player control remain live.
|
||||
// PLAYER COLLISION GATE — all movement is held while level-0 support collision is unresolved.
|
||||
TWeakObjectPtr<APawn> PawnTickPrerequisite;
|
||||
TWeakObjectPtr<UPawnMovementComponent> PawnMovementTickPrerequisite;
|
||||
TWeakObjectPtr<APawn> CollisionGatedPawn;
|
||||
@@ -1322,8 +1344,9 @@ public:
|
||||
uint8 SavedCharacterMovementMode = 0;
|
||||
uint8 SavedCharacterCustomMovementMode = 0;
|
||||
bool bSavedCharacterMovementMode = false;
|
||||
float SavedGateVerticalVelocity = 0.0f;
|
||||
bool bSavedGateVerticalVelocity = false;
|
||||
FVector SavedGateVelocity = FVector::ZeroVector;
|
||||
bool bSavedGateVelocity = false;
|
||||
bool bSavedPawnMovementWasActive = false;
|
||||
bool bPawnCollisionGateEngaged = false;
|
||||
double PawnGateEngagedAtSeconds = 0.0;
|
||||
bool bPawnGateUnavailableReported = false;
|
||||
|
||||
Reference in new issue
Block a user