wip(assembler): M1 round 6 -- owner walk fixes: 25 cm geometry scale, one height datum, carve/fill sign, faster relief meshing
Owner saw content floating above native terrain, spiky triangles, reversed carve/fill and late relief. Causes: shared mesher placed Core tiles on a 12.5 cm grid; content/atmosphere asked strate ranges in 12.5 cm voxels; Core edits add density where the legacy diff subtracts. Horizon LOD8 bridge edge 5.657 -> 2.265 cells. Relief mesh p95 60.8 -> 42.0 ms (LOD0). New BuildHost checks for scale, height, blueprint carve/fill and per-kind streaming lag. 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
f67b519e75
commit
61633cb492
18 files changed
+1708
-142
No files matched your search
@@ -3,13 +3,19 @@
|
||||
#include "VoxelForgeRegionEvalExperiment.h"
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "Async/ParallelFor.h"
|
||||
#include "Commandlets/Commandlet.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "Containers/StringConv.h"
|
||||
#include "Components/SceneComponent.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "GameFramework/GameModeBase.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
@@ -44,6 +50,7 @@
|
||||
#include "VoxelDensityAblation.h"
|
||||
#include "VoxelDensityProfile.h"
|
||||
#include "VoxelGenerator.h"
|
||||
#include "VoxelContentManager.h"
|
||||
#include "VoxelGeometryHash.h"
|
||||
#include "VoxelMarchingCubesMesher.h"
|
||||
#include "VoxelRedefinedDensityField.h"
|
||||
@@ -74,6 +81,445 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
enum class EAssemblerTileKind : uint8
|
||||
{
|
||||
FlatColumn,
|
||||
Relief,
|
||||
Mixed,
|
||||
Unknown
|
||||
};
|
||||
|
||||
struct FAssemblerTileTimingSample
|
||||
{
|
||||
int32 LOD = 0;
|
||||
EAssemblerTileKind Kind = EAssemblerTileKind::Unknown;
|
||||
double ServiceSeconds = 0.0;
|
||||
double ModelReadySeconds = 0.0;
|
||||
double ClassificationMilliseconds = 0.0;
|
||||
double ColumnMilliseconds = 0.0;
|
||||
double MeshingMilliseconds = 0.0;
|
||||
bool bHeightfieldBandLimited = false;
|
||||
};
|
||||
|
||||
const TCHAR* AssemblerTileKindName(EAssemblerTileKind Kind)
|
||||
{
|
||||
switch (Kind)
|
||||
{
|
||||
case EAssemblerTileKind::FlatColumn: return TEXT("flat_column");
|
||||
case EAssemblerTileKind::Relief: return TEXT("relief");
|
||||
case EAssemblerTileKind::Mixed: return TEXT("mixed");
|
||||
default: return TEXT("unknown");
|
||||
}
|
||||
}
|
||||
|
||||
EAssemblerTileKind ClassifyAssemblerTile(const VoxelForgeCore::TileMesh& Mesh)
|
||||
{
|
||||
const VoxelForgeCore::TileGenerationStats& Stats = Mesh.diagnostics;
|
||||
if (Stats.featureRefinedResolution)
|
||||
{
|
||||
return EAssemblerTileKind::Relief;
|
||||
}
|
||||
|
||||
double MinGroundZ = std::numeric_limits<double>::infinity();
|
||||
double MaxGroundZ = -std::numeric_limits<double>::infinity();
|
||||
const std::size_t GroundIndexCount = std::min<std::size_t>(
|
||||
Mesh.indices.size(), static_cast<std::size_t>(Mesh.groups.groundTriangleCount) * 3u);
|
||||
const std::size_t ProbeCount = std::min<std::size_t>(64u, GroundIndexCount);
|
||||
for (std::size_t Probe = 0; Probe < ProbeCount; ++Probe)
|
||||
{
|
||||
const std::size_t Index = ProbeCount <= 1u ? 0u
|
||||
: (Probe * (GroundIndexCount - 1u)) / (ProbeCount - 1u);
|
||||
const std::uint32_t VertexIndex = Mesh.indices[Index];
|
||||
if (VertexIndex >= Mesh.vertices.size())
|
||||
{
|
||||
return EAssemblerTileKind::Unknown;
|
||||
}
|
||||
const double Z = Mesh.vertices[VertexIndex].position.z;
|
||||
if (!std::isfinite(Z))
|
||||
{
|
||||
return EAssemblerTileKind::Unknown;
|
||||
}
|
||||
MinGroundZ = std::min(MinGroundZ, Z);
|
||||
MaxGroundZ = std::max(MaxGroundZ, Z);
|
||||
}
|
||||
const int32 LOD = FMath::Clamp(static_cast<int32>(Mesh.key.lod), 0, 20);
|
||||
const double ReliefThresholdCm = 4.0 * 25.0 * static_cast<double>(1u << LOD);
|
||||
if (std::isfinite(MinGroundZ) && MaxGroundZ - MinGroundZ > ReliefThresholdCm)
|
||||
{
|
||||
return EAssemblerTileKind::Relief;
|
||||
}
|
||||
if (Stats.meshingSkipped || Stats.tileClass == VoxelForgeCore::TileClass::allAir
|
||||
|| Stats.tileClass == VoxelForgeCore::TileClass::allSolid)
|
||||
{
|
||||
return EAssemblerTileKind::FlatColumn;
|
||||
}
|
||||
if (Stats.tileClass == VoxelForgeCore::TileClass::mixed || !Mesh.indices.empty())
|
||||
{
|
||||
return EAssemblerTileKind::Mixed;
|
||||
}
|
||||
if (Stats.unknownBoxes > 0 || Stats.tileClass == VoxelForgeCore::TileClass::unknown)
|
||||
{
|
||||
return EAssemblerTileKind::Unknown;
|
||||
}
|
||||
return EAssemblerTileKind::Unknown;
|
||||
}
|
||||
|
||||
bool TryParseBlueprintPinNumber(const UEdGraphPin* Pin, double& OutValue)
|
||||
{
|
||||
if (Pin == nullptr || Pin->DefaultValue.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return LexTryParseString(OutValue, *Pin->DefaultValue) && std::isfinite(OutValue);
|
||||
}
|
||||
|
||||
bool ReadStagedPlayerSpeeds(float& OutWalkCmPerSecond, float& OutSprintCmPerSecond,
|
||||
FString& OutError)
|
||||
{
|
||||
const UClass* CharacterClass = LoadClass<ACharacter>(nullptr,
|
||||
TEXT("/Game/FirstPerson/Blueprints/BP_FirstPersonCharacter.BP_FirstPersonCharacter_C"));
|
||||
const UClass* ControllerClass = LoadClass<APlayerController>(nullptr,
|
||||
TEXT("/Game/FirstPerson/Blueprints/BP_FirstPersonPlayerController.BP_FirstPersonPlayerController_C"));
|
||||
const ACharacter* CharacterDefault = CharacterClass
|
||||
? Cast<ACharacter>(CharacterClass->GetDefaultObject()) : nullptr;
|
||||
if (CharacterDefault == nullptr || CharacterDefault->GetCharacterMovement() == nullptr)
|
||||
{
|
||||
OutError = TEXT("BuildHost staged BP_FirstPersonCharacter CDO or CharacterMovement is missing.");
|
||||
return false;
|
||||
}
|
||||
|
||||
OutWalkCmPerSecond = CharacterDefault->GetCharacterMovement()->MaxWalkSpeed;
|
||||
double BestSprintSpeed = 0.0;
|
||||
FString BestSprintSource;
|
||||
const UBlueprint* CharacterBlueprint = LoadObject<UBlueprint>(nullptr,
|
||||
TEXT("/Game/FirstPerson/Blueprints/BP_FirstPersonCharacter.BP_FirstPersonCharacter"));
|
||||
const UBlueprint* ControllerBlueprint = LoadObject<UBlueprint>(nullptr,
|
||||
TEXT("/Game/FirstPerson/Blueprints/BP_FirstPersonPlayerController.BP_FirstPersonPlayerController"));
|
||||
const auto ScanBlueprintGraphs = [&](const UBlueprint* Blueprint, const TCHAR* BlueprintName)
|
||||
{
|
||||
if (Blueprint == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const auto ScanGraph = [&](const UEdGraph* Graph)
|
||||
{
|
||||
if (Graph == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (const UEdGraphNode* Node : Graph->Nodes)
|
||||
{
|
||||
if (Node == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (const UEdGraphPin* Pin : Node->Pins)
|
||||
{
|
||||
if (Pin == nullptr || Pin->Direction != EGPD_Input
|
||||
|| !Pin->PinName.ToString().Contains(TEXT("MaxWalkSpeed"), ESearchCase::IgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
double Value = 0.0;
|
||||
bool bFoundValue = TryParseBlueprintPinNumber(Pin, Value);
|
||||
if (!bFoundValue)
|
||||
{
|
||||
for (const UEdGraphPin* LinkedPin : Pin->LinkedTo)
|
||||
{
|
||||
if (TryParseBlueprintPinNumber(LinkedPin, Value))
|
||||
{
|
||||
bFoundValue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bFoundValue && Value > static_cast<double>(OutWalkCmPerSecond)
|
||||
&& Value > BestSprintSpeed)
|
||||
{
|
||||
BestSprintSpeed = Value;
|
||||
BestSprintSource = FString::Printf(TEXT("%s.%s"), BlueprintName,
|
||||
*Node->GetName());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const auto& GraphReference : Blueprint->UbergraphPages)
|
||||
{
|
||||
ScanGraph(GraphReference);
|
||||
}
|
||||
for (const auto& GraphReference : Blueprint->FunctionGraphs)
|
||||
{
|
||||
ScanGraph(GraphReference);
|
||||
}
|
||||
};
|
||||
ScanBlueprintGraphs(CharacterBlueprint, TEXT("BP_FirstPersonCharacter"));
|
||||
ScanBlueprintGraphs(ControllerBlueprint, TEXT("BP_FirstPersonPlayerController"));
|
||||
|
||||
if (!std::isfinite(OutWalkCmPerSecond) || OutWalkCmPerSecond <= 0.0f
|
||||
|| !std::isfinite(BestSprintSpeed) || BestSprintSpeed <= OutWalkCmPerSecond)
|
||||
{
|
||||
OutError = TEXT("BuildHost staged Blueprint graphs do not expose a MaxWalkSpeed input above the character CDO walk speed.");
|
||||
return false;
|
||||
}
|
||||
OutSprintCmPerSecond = static_cast<float>(BestSprintSpeed);
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgePawnSpeedFixture] source=staged_blueprints walk_cm_s=%.3f sprint_cm_s=%.3f sprint_graph=%s"),
|
||||
OutWalkCmPerSecond, OutSprintCmPerSecond, *BestSprintSource);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct FAssemblerGeometryAudit
|
||||
{
|
||||
bool bValid = false;
|
||||
int32 LOD = 0;
|
||||
FIntVector Tile = FIntVector::ZeroValue;
|
||||
double ExpectedTileSpanCm = 0.0;
|
||||
double CellWorldCm = 0.0;
|
||||
double SpanXcm = 0.0;
|
||||
double SpanYcm = 0.0;
|
||||
double MaxTriangleEdgeCm = 0.0;
|
||||
VoxelForgeCore::Vec3d MaxEdgeStart;
|
||||
VoxelForgeCore::Vec3d MaxEdgeEnd;
|
||||
std::uint64_t MaxEdgeTriangleIndex = 0;
|
||||
int32 NormalSamples = 0;
|
||||
int32 NormalOrientationMismatches = 0;
|
||||
int32 TriangleSamples = 0;
|
||||
int32 TriangleWindingMismatches = 0;
|
||||
int32 SkirtTrianglesExcluded = 0;
|
||||
};
|
||||
|
||||
bool AuditAssemblerCoreTile(const VoxelForgeCore::World& World,
|
||||
const VoxelForgeCore::TileMesh& Mesh,
|
||||
FAssemblerGeometryAudit& OutAudit,
|
||||
FString& OutError)
|
||||
{
|
||||
if (Mesh.indices.empty() || Mesh.indices.size() % 3u != 0u || Mesh.vertices.empty())
|
||||
{
|
||||
OutError = TEXT("Core geometry audit received a malformed triangle mesh.");
|
||||
return false;
|
||||
}
|
||||
const std::size_t SurfaceVertexCount = std::min<std::size_t>(
|
||||
Mesh.vertices.size(), Mesh.groups.surfaceVertexCount);
|
||||
if (SurfaceVertexCount == 0u)
|
||||
{
|
||||
OutError = TEXT("Core geometry audit mesh contains no surface vertices.");
|
||||
return false;
|
||||
}
|
||||
|
||||
double MinX = std::numeric_limits<double>::infinity();
|
||||
double MaxX = -std::numeric_limits<double>::infinity();
|
||||
double MinY = std::numeric_limits<double>::infinity();
|
||||
double MaxY = -std::numeric_limits<double>::infinity();
|
||||
for (std::size_t Index = 0; Index < SurfaceVertexCount; ++Index)
|
||||
{
|
||||
const VoxelForgeCore::MeshVertex& Vertex = Mesh.vertices[Index];
|
||||
if (!std::isfinite(Vertex.position.x) || !std::isfinite(Vertex.position.y)
|
||||
|| !std::isfinite(Vertex.position.z))
|
||||
{
|
||||
OutError = TEXT("Core geometry audit found a non-finite surface vertex.");
|
||||
return false;
|
||||
}
|
||||
MinX = std::min(MinX, Vertex.position.x);
|
||||
MaxX = std::max(MaxX, Vertex.position.x);
|
||||
MinY = std::min(MinY, Vertex.position.y);
|
||||
MaxY = std::max(MaxY, Vertex.position.y);
|
||||
}
|
||||
|
||||
const int32 LOD = static_cast<int32>(Mesh.key.lod);
|
||||
const VoxelForgeCore::WorldConfiguration& Configuration = World.configuration();
|
||||
const int32 ExtentVoxels = CHUNK_SIZE << FMath::Clamp(LOD, 0, 20);
|
||||
const int32 Cells = LOD < Configuration.fullResolutionLodCount
|
||||
? CHUNK_SIZE : FMath::Max(1, Configuration.coarseTileCells);
|
||||
const double Step = static_cast<double>(FMath::Max(1, ExtentVoxels / Cells));
|
||||
const int32 StepVoxels = FMath::Max(1, FMath::RoundToInt(Step));
|
||||
const double CellWorldCm = 25.0 * Step;
|
||||
// Les coutures lointaines peuvent depasser deux cellules en plan sans former une pointe verticale.
|
||||
// Far LOD transitions can span slightly over two horizontal cells without creating a vertical spike.
|
||||
const double MaxEdgeCellMultiple = LOD >= 7 ? 4.0 : 2.0;
|
||||
const double MaxEdgeWorldCm = MaxEdgeCellMultiple * CellWorldCm;
|
||||
const int32 PreviousOctaveBias = VoxelGenLOD::GetThreadOctaveBias();
|
||||
const int32 PreviousSampleStep = VoxelGenLOD::GetThreadSampleStep();
|
||||
struct FRestoreAssemblerLodSampleState
|
||||
{
|
||||
int32 OctaveBias;
|
||||
int32 SampleStep;
|
||||
~FRestoreAssemblerLodSampleState()
|
||||
{
|
||||
VoxelGenLOD::SetThreadOctaveBias(OctaveBias);
|
||||
VoxelGenLOD::SetThreadSampleStep(SampleStep);
|
||||
}
|
||||
} RestoreLodSampleState{PreviousOctaveBias, PreviousSampleStep};
|
||||
const int32 OctaveBias = Configuration.lodOctaveDrop > 0 && StepVoxels > 1
|
||||
? Configuration.lodOctaveDrop * static_cast<int32>(FMath::FloorLog2(
|
||||
static_cast<uint32>(StepVoxels))) : 0;
|
||||
VoxelGenLOD::SetThreadOctaveBias(OctaveBias);
|
||||
VoxelGenLOD::SetThreadSampleStep(StepVoxels);
|
||||
for (std::size_t Index = 0; Index < Mesh.indices.size(); Index += 3u)
|
||||
{
|
||||
const std::size_t TriangleIndex = Index / 3u;
|
||||
if (TriangleIndex < Mesh.skirtFaceMasks.size()
|
||||
&& Mesh.skirtFaceMasks[TriangleIndex] != 0u)
|
||||
{
|
||||
++OutAudit.SkirtTrianglesExcluded;
|
||||
continue;
|
||||
}
|
||||
const std::uint32_t IA = Mesh.indices[Index];
|
||||
const std::uint32_t IB = Mesh.indices[Index + 1u];
|
||||
const std::uint32_t IC = Mesh.indices[Index + 2u];
|
||||
if (IA >= Mesh.vertices.size() || IB >= Mesh.vertices.size() || IC >= Mesh.vertices.size())
|
||||
{
|
||||
OutError = TEXT("Core geometry audit found an out-of-range triangle index.");
|
||||
return false;
|
||||
}
|
||||
const auto& A = Mesh.vertices[IA].position;
|
||||
const auto& B = Mesh.vertices[IB].position;
|
||||
const auto& C = Mesh.vertices[IC].position;
|
||||
const auto Distance = [](const auto& P, const auto& Q)
|
||||
{
|
||||
const double DX = P.x - Q.x;
|
||||
const double DY = P.y - Q.y;
|
||||
const double DZ = P.z - Q.z;
|
||||
return std::sqrt(DX * DX + DY * DY + DZ * DZ);
|
||||
};
|
||||
const double EdgeAB = Distance(A, B);
|
||||
const double EdgeBC = Distance(B, C);
|
||||
const double EdgeCA = Distance(C, A);
|
||||
const double Edge = std::max({EdgeAB, EdgeBC, EdgeCA});
|
||||
const VoxelForgeCore::Vec3d* EdgeStart = &A;
|
||||
const VoxelForgeCore::Vec3d* EdgeEnd = &B;
|
||||
if (EdgeBC >= EdgeAB && EdgeBC >= EdgeCA)
|
||||
{
|
||||
EdgeStart = &B;
|
||||
EdgeEnd = &C;
|
||||
}
|
||||
else if (EdgeCA >= EdgeAB)
|
||||
{
|
||||
EdgeStart = &C;
|
||||
EdgeEnd = &A;
|
||||
}
|
||||
if (!std::isfinite(Edge) || Edge > MaxEdgeWorldCm + 0.1)
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core geometry audit found an oversized edge %.3f cm (bound %.3f cm; delta=(%.3f,%.3f,%.3f), start=(%.3f,%.3f,%.3f), end=(%.3f,%.3f,%.3f)) in tile (%d,%d,%d) lod=%d triangle=%llu."),
|
||||
Edge, MaxEdgeWorldCm, EdgeEnd->x - EdgeStart->x, EdgeEnd->y - EdgeStart->y,
|
||||
EdgeEnd->z - EdgeStart->z, EdgeStart->x, EdgeStart->y, EdgeStart->z,
|
||||
EdgeEnd->x, EdgeEnd->y, EdgeEnd->z, Mesh.key.coordinate.x,
|
||||
Mesh.key.coordinate.y, Mesh.key.coordinate.z, LOD,
|
||||
static_cast<unsigned long long>(TriangleIndex));
|
||||
return false;
|
||||
}
|
||||
if (Edge > OutAudit.MaxTriangleEdgeCm)
|
||||
{
|
||||
OutAudit.MaxTriangleEdgeCm = Edge;
|
||||
OutAudit.MaxEdgeTriangleIndex = static_cast<std::uint64_t>(TriangleIndex);
|
||||
if (EdgeAB >= EdgeBC && EdgeAB >= EdgeCA)
|
||||
{
|
||||
OutAudit.MaxEdgeStart = A;
|
||||
OutAudit.MaxEdgeEnd = B;
|
||||
}
|
||||
else if (EdgeBC >= EdgeCA)
|
||||
{
|
||||
OutAudit.MaxEdgeStart = B;
|
||||
OutAudit.MaxEdgeEnd = C;
|
||||
}
|
||||
else
|
||||
{
|
||||
OutAudit.MaxEdgeStart = C;
|
||||
OutAudit.MaxEdgeEnd = A;
|
||||
}
|
||||
}
|
||||
|
||||
const double ABX = B.x - A.x, ABY = B.y - A.y, ABZ = B.z - A.z;
|
||||
const double ACX = C.x - A.x, ACY = C.y - A.y, ACZ = C.z - A.z;
|
||||
const double FaceX = ABY * ACZ - ABZ * ACY;
|
||||
const double FaceY = ABZ * ACX - ABX * ACZ;
|
||||
const double FaceZ = ABX * ACY - ABY * ACX;
|
||||
const VoxelForgeCore::MeshVertex& NormalA = Mesh.vertices[IA];
|
||||
const VoxelForgeCore::MeshVertex& NormalB = Mesh.vertices[IB];
|
||||
const VoxelForgeCore::MeshVertex& NormalC = Mesh.vertices[IC];
|
||||
const double Alignment = FaceX * (NormalA.normal.x + NormalB.normal.x + NormalC.normal.x)
|
||||
+ FaceY * (NormalA.normal.y + NormalB.normal.y + NormalC.normal.y)
|
||||
+ FaceZ * (NormalA.normal.z + NormalB.normal.z + NormalC.normal.z);
|
||||
const double FaceLengthSq = FaceX * FaceX + FaceY * FaceY + FaceZ * FaceZ;
|
||||
if (FaceLengthSq > 1.0e-10)
|
||||
{
|
||||
if (Alignment < -1.0e-10 * std::sqrt(FaceLengthSq))
|
||||
{
|
||||
// RealtimeMesh requires MC indices in reversed 0,2,1 order; this reports the expected convention.
|
||||
// RealtimeMesh exige l'ordre MC inverse 0,2,1; ce compte documente la convention attendue.
|
||||
++OutAudit.TriangleWindingMismatches;
|
||||
}
|
||||
++OutAudit.TriangleSamples;
|
||||
}
|
||||
}
|
||||
|
||||
const std::size_t NormalProbeCount = std::min<std::size_t>(96u, SurfaceVertexCount);
|
||||
for (std::size_t Probe = 0; Probe < NormalProbeCount; ++Probe)
|
||||
{
|
||||
const std::size_t Index = NormalProbeCount <= 1u ? 0u
|
||||
: Probe * (SurfaceVertexCount - 1u) / (NormalProbeCount - 1u);
|
||||
const VoxelForgeCore::MeshVertex& Vertex = Mesh.vertices[Index];
|
||||
const double NX = Vertex.normal.x;
|
||||
const double NY = Vertex.normal.y;
|
||||
const double NZ = Vertex.normal.z;
|
||||
const double NormalLengthSq = NX * NX + NY * NY + NZ * NZ;
|
||||
if (!std::isfinite(NormalLengthSq) || std::abs(NormalLengthSq - 1.0) > 0.02)
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core geometry audit found a non-unit normal (length squared %.5f)."),
|
||||
NormalLengthSq);
|
||||
return false;
|
||||
}
|
||||
// Garder la sonde locale evite de traverser plusieurs reliefs aux LOD lointains.
|
||||
// Keep the probe local so coarse-LOD checks do not cross several terrain features.
|
||||
if (LOD <= 1)
|
||||
{
|
||||
// Le champ complet ne represente pas la surface lointaine decimee par le LOD.
|
||||
// The full-resolution field does not represent the decimated far-LOD surface.
|
||||
const double NormalOffsetVoxels = std::min(0.5 * Step, 1.0);
|
||||
const VoxelForgeCore::Vec3d Positive{
|
||||
Vertex.position.x / 25.0 + NX * NormalOffsetVoxels,
|
||||
Vertex.position.y / 25.0 + NY * NormalOffsetVoxels,
|
||||
Vertex.position.z / 25.0 + NZ * NormalOffsetVoxels};
|
||||
const VoxelForgeCore::Vec3d Negative{
|
||||
Vertex.position.x / 25.0 - NX * NormalOffsetVoxels,
|
||||
Vertex.position.y / 25.0 - NY * NormalOffsetVoxels,
|
||||
Vertex.position.z / 25.0 - NZ * NormalOffsetVoxels};
|
||||
const float PositiveDensity = World.densityAt(Positive);
|
||||
const float NegativeDensity = World.densityAt(Negative);
|
||||
if (PositiveDensity + 0.01f < NegativeDensity)
|
||||
{
|
||||
++OutAudit.NormalOrientationMismatches;
|
||||
}
|
||||
++OutAudit.NormalSamples;
|
||||
}
|
||||
}
|
||||
if (OutAudit.NormalSamples > 0
|
||||
&& OutAudit.NormalOrientationMismatches * 10 > OutAudit.NormalSamples)
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core geometry audit found %d/%d normals pointing toward denser material in tile (%d,%d,%d) lod=%d step=%d octave_bias=%d."),
|
||||
OutAudit.NormalOrientationMismatches, OutAudit.NormalSamples,
|
||||
Mesh.key.coordinate.x, Mesh.key.coordinate.y, Mesh.key.coordinate.z,
|
||||
LOD, StepVoxels, OctaveBias);
|
||||
return false;
|
||||
}
|
||||
|
||||
OutAudit.bValid = true;
|
||||
OutAudit.LOD = LOD;
|
||||
OutAudit.Tile = FIntVector(static_cast<int32>(Mesh.key.coordinate.x),
|
||||
static_cast<int32>(Mesh.key.coordinate.y),
|
||||
static_cast<int32>(Mesh.key.coordinate.z));
|
||||
OutAudit.ExpectedTileSpanCm = static_cast<double>(ExtentVoxels) * 25.0;
|
||||
OutAudit.CellWorldCm = CellWorldCm;
|
||||
OutAudit.SpanXcm = MaxX - MinX;
|
||||
OutAudit.SpanYcm = MaxY - MinY;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
using FExploreJsonWriter = TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>;
|
||||
@@ -13205,6 +13651,14 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
return false;
|
||||
}
|
||||
|
||||
float StagedWalkSpeedCmPerSecond = 0.0f;
|
||||
float StagedSprintSpeedCmPerSecond = 0.0f;
|
||||
if (!ReadStagedPlayerSpeeds(
|
||||
StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond, OutError))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Arguments.CoreWorldAssemblyManifestPath.IsEmpty())
|
||||
{
|
||||
const FString ManifestPath = FPaths::ConvertRelativePathToFull(
|
||||
@@ -13265,7 +13719,7 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
TArray<FVoxelTileKey> ExpectedAffectedTiles;
|
||||
FVector CarveLocalPositionCm = FVector::ZeroVector;
|
||||
FVoxelTileKey CarveTargetTile;
|
||||
constexpr float CarveBrushRadiusLegacyVoxels = 5.0f;
|
||||
constexpr float CarveBrushRadiusLegacyVoxels = 6.0f;
|
||||
constexpr double CoreVoxelSizeCm = 25.0;
|
||||
const double CarveBrushRadiusCoreVoxels =
|
||||
CarveBrushRadiusLegacyVoxels * static_cast<double>(VOXEL_SIZE) / CoreVoxelSizeCm;
|
||||
@@ -13280,6 +13734,8 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
VoxelForgeCore::ShapeBuildRequest MatchedTimingInitialRequest;
|
||||
double NativeSurfaceRegionCompileSeconds = 0.0;
|
||||
double NativeSurfaceTileGenerationSeconds = 0.0;
|
||||
std::vector<FAssemblerTileTimingSample> NativeSurfaceTileSamples;
|
||||
std::vector<FAssemblerTileTimingSample> BridgeSurfaceTileSamples;
|
||||
std::uint64_t NativeSurfaceTriangleCount = 0;
|
||||
std::size_t ExpectedInitialTileCount = 0;
|
||||
std::size_t ExpectedTravelTileCount = 0;
|
||||
@@ -13637,7 +14093,7 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
Brush.halfExtent = {5.0, 5.0, 5.0};
|
||||
Brush.radius = CarveBrushRadiusCoreVoxels;
|
||||
Brush.falloff = 2.0f;
|
||||
Brush.strength = -40.0f;
|
||||
Brush.strength = 5.0f;
|
||||
Transaction.brushes.push_back(Brush);
|
||||
const std::optional<VoxelForgeCore::EditResult> Edit =
|
||||
ReferenceWorld.applyEditTransaction(Transaction, Desired, CoreError);
|
||||
@@ -13821,6 +14277,17 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
SurfaceTravelRegionsBuilt = SurfaceTravelCompileStats.regionsBuilt;
|
||||
std::vector<FStandaloneCoreAdapterTileHashes> SurfaceTravelRows;
|
||||
SurfaceTravelRows.reserve(SurfaceTravelDesired.size());
|
||||
FAssemblerGeometryAudit GeometryAuditL0;
|
||||
FAssemblerGeometryAudit GeometryAuditL1;
|
||||
FAssemblerGeometryAudit GeometryAuditHorizon;
|
||||
int32 GeometryAuditL0Distance = MAX_int32;
|
||||
int32 GeometryAuditL1Distance = MAX_int32;
|
||||
int32 GeometryAuditHorizonDistance = MAX_int32;
|
||||
int32 HorizonAuditLOD = 0;
|
||||
for (const VoxelForgeCore::TileKey& Key : SurfaceTravelDesired)
|
||||
{
|
||||
HorizonAuditLOD = FMath::Max(HorizonAuditLOD, static_cast<int32>(Key.lod));
|
||||
}
|
||||
for (const VoxelForgeCore::TileKey& Key : SurfaceTravelDesired)
|
||||
{
|
||||
const double TileGenerationStart = FPlatformTime::Seconds();
|
||||
@@ -13833,8 +14300,52 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
UTF8_TO_TCHAR(CoreError.c_str()));
|
||||
return false;
|
||||
}
|
||||
NativeSurfaceTileGenerationSeconds += FPlatformTime::Seconds()
|
||||
- TileGenerationStart;
|
||||
if ((Key.lod <= 1u || static_cast<int32>(Key.lod) == HorizonAuditLOD)
|
||||
&& Mesh->groups.groundTriangleCount > 0u)
|
||||
{
|
||||
const int32 LOD = static_cast<int32>(Key.lod);
|
||||
const int32 ExtentVoxels = CHUNK_SIZE << LOD;
|
||||
const FIntVector TargetTile(
|
||||
FMath::FloorToInt(SurfaceCameraVoxels.x / ExtentVoxels),
|
||||
FMath::FloorToInt(SurfaceCameraVoxels.y / ExtentVoxels),
|
||||
FMath::FloorToInt(SurfaceCameraVoxels.z / ExtentVoxels));
|
||||
const FIntVector TileCoord(
|
||||
static_cast<int32>(Key.coordinate.x), static_cast<int32>(Key.coordinate.y),
|
||||
static_cast<int32>(Key.coordinate.z));
|
||||
const int32 Distance = FMath::Abs(TileCoord.X - TargetTile.X)
|
||||
+ FMath::Abs(TileCoord.Y - TargetTile.Y) + FMath::Abs(TileCoord.Z - TargetTile.Z);
|
||||
FAssemblerGeometryAudit Candidate;
|
||||
FString GeometryError;
|
||||
if (!AuditAssemblerCoreTile(ReferenceWorld, *Mesh, Candidate, GeometryError))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Native surface mesh geometry audit failed: %s"),
|
||||
*GeometryError);
|
||||
return false;
|
||||
}
|
||||
const double SpanRatioX = Candidate.SpanXcm / Candidate.ExpectedTileSpanCm;
|
||||
const double SpanRatioY = Candidate.SpanYcm / Candidate.ExpectedTileSpanCm;
|
||||
if (SpanRatioX >= 0.75 && SpanRatioY >= 0.75
|
||||
&& SpanRatioX <= 1.05 && SpanRatioY <= 1.05)
|
||||
{
|
||||
if (LOD == 0 && Distance < GeometryAuditL0Distance)
|
||||
{
|
||||
GeometryAuditL0 = Candidate;
|
||||
GeometryAuditL0Distance = Distance;
|
||||
}
|
||||
else if (LOD == 1 && Distance < GeometryAuditL1Distance)
|
||||
{
|
||||
GeometryAuditL1 = Candidate;
|
||||
GeometryAuditL1Distance = Distance;
|
||||
}
|
||||
if (LOD == HorizonAuditLOD && Distance < GeometryAuditHorizonDistance)
|
||||
{
|
||||
GeometryAuditHorizon = Candidate;
|
||||
GeometryAuditHorizonDistance = Distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
const double TileGenerationSeconds = FPlatformTime::Seconds() - TileGenerationStart;
|
||||
NativeSurfaceTileGenerationSeconds += TileGenerationSeconds;
|
||||
NativeSurfaceTriangleCount += Mesh->indices.size() / 3;
|
||||
const FVoxelTileKey RuntimeKey(
|
||||
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
|
||||
@@ -13851,6 +14362,80 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
{Record.Density, Record.Mesh, Record.Payload, Record.Topology}});
|
||||
if (Mesh->groups.skirtTriangleCount > 0) ++SurfaceTravelTilesWithSkirts;
|
||||
}
|
||||
VoxelForgeCore::TileGenerationOptions NativeTimingOptions;
|
||||
for (const VoxelForgeCore::TileKey& Key : SurfaceTravelDesired)
|
||||
{
|
||||
if (Key.lod > 1u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const double TileGenerationStart = FPlatformTime::Seconds();
|
||||
const std::optional<VoxelForgeCore::TileMesh> Mesh =
|
||||
ReferenceWorld.generateTile(Key, NativeTimingOptions, CoreError);
|
||||
if (!Mesh)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Native unhashed streaming sample failed: %s"),
|
||||
UTF8_TO_TCHAR(CoreError.c_str()));
|
||||
return false;
|
||||
}
|
||||
const double TileGenerationSeconds = FPlatformTime::Seconds() - TileGenerationStart;
|
||||
NativeSurfaceTileSamples.push_back({
|
||||
static_cast<int32>(Key.lod), ClassifyAssemblerTile(*Mesh), TileGenerationSeconds,
|
||||
0.0, Mesh->diagnostics.classificationMilliseconds,
|
||||
Mesh->diagnostics.columnComputeMilliseconds, Mesh->diagnostics.meshingMilliseconds,
|
||||
Mesh->diagnostics.heightfieldBandLimited});
|
||||
}
|
||||
if (!GeometryAuditL0.bValid || !GeometryAuditL1.bValid || !GeometryAuditHorizon.bValid)
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Native geometry audit missed full-span tiles (l0=%d l1=%d horizon_lod=%d horizon=%d)."),
|
||||
GeometryAuditL0.bValid ? 1 : 0, GeometryAuditL1.bValid ? 1 : 0,
|
||||
HorizonAuditLOD, GeometryAuditHorizon.bValid ? 1 : 0);
|
||||
return false;
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldTest] geometry_scale_agreement=PASS legacy_default_cm=%.1f core_voxel_cm=%.1f "
|
||||
"pre_fix_scale_ratio=%.4f lod0_expected_cm=%.1f span_cm=(%.1f,%.1f) ratio=(%.4f,%.4f) max_edge_cm=%.2f normal_samples=%d normal_mismatches=%d rmesh_reversed_winding=%d/%d skirts_excluded=%d "
|
||||
"lod1_expected_cm=%.1f span_cm=(%.1f,%.1f) ratio=(%.4f,%.4f) max_edge_cm=%.2f normal_samples=%d normal_mismatches=%d rmesh_reversed_winding=%d/%d skirts_excluded=%d "
|
||||
"horizon_lod=%d expected_cm=%.1f span_cm=(%.1f,%.1f) ratio=(%.4f,%.4f) max_edge_cm=%.2f normal_samples=%d normal_mismatches=%d rmesh_reversed_winding=%d/%d skirts_excluded=%d"),
|
||||
VOXEL_SIZE, CoreVoxelSizeCm,
|
||||
VOXEL_SIZE / CoreVoxelSizeCm,
|
||||
GeometryAuditL0.ExpectedTileSpanCm, GeometryAuditL0.SpanXcm, GeometryAuditL0.SpanYcm,
|
||||
GeometryAuditL0.SpanXcm / GeometryAuditL0.ExpectedTileSpanCm,
|
||||
GeometryAuditL0.SpanYcm / GeometryAuditL0.ExpectedTileSpanCm,
|
||||
GeometryAuditL0.MaxTriangleEdgeCm, GeometryAuditL0.NormalSamples,
|
||||
GeometryAuditL0.NormalOrientationMismatches, GeometryAuditL0.TriangleWindingMismatches,
|
||||
GeometryAuditL0.TriangleSamples, GeometryAuditL0.SkirtTrianglesExcluded,
|
||||
GeometryAuditL1.ExpectedTileSpanCm, GeometryAuditL1.SpanXcm, GeometryAuditL1.SpanYcm,
|
||||
GeometryAuditL1.SpanXcm / GeometryAuditL1.ExpectedTileSpanCm,
|
||||
GeometryAuditL1.SpanYcm / GeometryAuditL1.ExpectedTileSpanCm,
|
||||
GeometryAuditL1.MaxTriangleEdgeCm, GeometryAuditL1.NormalSamples,
|
||||
GeometryAuditL1.NormalOrientationMismatches, GeometryAuditL1.TriangleWindingMismatches,
|
||||
GeometryAuditL1.TriangleSamples, GeometryAuditL1.SkirtTrianglesExcluded,
|
||||
HorizonAuditLOD, GeometryAuditHorizon.ExpectedTileSpanCm,
|
||||
GeometryAuditHorizon.SpanXcm, GeometryAuditHorizon.SpanYcm,
|
||||
GeometryAuditHorizon.SpanXcm / GeometryAuditHorizon.ExpectedTileSpanCm,
|
||||
GeometryAuditHorizon.SpanYcm / GeometryAuditHorizon.ExpectedTileSpanCm,
|
||||
GeometryAuditHorizon.MaxTriangleEdgeCm, GeometryAuditHorizon.NormalSamples,
|
||||
GeometryAuditHorizon.NormalOrientationMismatches,
|
||||
GeometryAuditHorizon.TriangleWindingMismatches, GeometryAuditHorizon.TriangleSamples,
|
||||
GeometryAuditHorizon.SkirtTrianglesExcluded);
|
||||
const auto LogGeometryAuditTile = [](const FAssemblerGeometryAudit& Audit)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeGeometryAudit] lod=%d tile=(%d,%d,%d) max_edge_cm=%.3f edge_cells=%.3f triangle=%llu endpoints_cm=((%.2f,%.2f,%.2f),(%.2f,%.2f,%.2f)) normal=%d/%d rmesh_reversed_winding=%d/%d skirt_triangles_excluded=%d"),
|
||||
Audit.LOD, Audit.Tile.X, Audit.Tile.Y, Audit.Tile.Z,
|
||||
Audit.MaxTriangleEdgeCm, Audit.MaxTriangleEdgeCm / FMath::Max(Audit.CellWorldCm, 1.0),
|
||||
static_cast<unsigned long long>(Audit.MaxEdgeTriangleIndex),
|
||||
Audit.MaxEdgeStart.x, Audit.MaxEdgeStart.y, Audit.MaxEdgeStart.z,
|
||||
Audit.MaxEdgeEnd.x, Audit.MaxEdgeEnd.y, Audit.MaxEdgeEnd.z,
|
||||
Audit.NormalOrientationMismatches, Audit.NormalSamples,
|
||||
Audit.TriangleWindingMismatches, Audit.TriangleSamples,
|
||||
Audit.SkirtTrianglesExcluded);
|
||||
};
|
||||
LogGeometryAuditTile(GeometryAuditL0);
|
||||
LogGeometryAuditTile(GeometryAuditL1);
|
||||
LogGeometryAuditTile(GeometryAuditHorizon);
|
||||
const FIntVector SurfaceCollisionCenter(
|
||||
FMath::FloorToInt(SurfaceCameraVoxels.x / CHUNK_SIZE),
|
||||
FMath::FloorToInt(SurfaceCameraVoxels.y / CHUNK_SIZE),
|
||||
@@ -13965,7 +14550,6 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
const double TileGenerationStart = FPlatformTime::Seconds();
|
||||
const std::optional<VoxelForgeCore::TileMesh> Mesh =
|
||||
BridgeWorld.generateTile(Key, BridgeTileOptions, BridgeError);
|
||||
BridgeSurfaceTileGenerationSeconds += FPlatformTime::Seconds() - TileGenerationStart;
|
||||
if (!Mesh || !Mesh->hasDensityHash || !Mesh->hasMeshHash
|
||||
|| !Mesh->hasPayloadHash || !Mesh->hasTopologyHash)
|
||||
{
|
||||
@@ -13973,6 +14557,8 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
UTF8_TO_TCHAR(BridgeError.c_str()));
|
||||
return false;
|
||||
}
|
||||
const double TileGenerationSeconds = FPlatformTime::Seconds() - TileGenerationStart;
|
||||
BridgeSurfaceTileGenerationSeconds += TileGenerationSeconds;
|
||||
BridgeSurfaceTriangleCount += Mesh->indices.size() / 3;
|
||||
++BridgeSurfaceTilesGenerated;
|
||||
}
|
||||
@@ -13981,6 +14567,97 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
OutError = TEXT("Matched legacy-bridge timing did not generate the complete surface key set.");
|
||||
return false;
|
||||
}
|
||||
VoxelForgeCore::TileGenerationOptions BridgeTimingOptions;
|
||||
for (const VoxelForgeCore::TileKey& Key : MatchedTimingSurfaceDesired)
|
||||
{
|
||||
if (Key.lod > 1u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const double TileGenerationStart = FPlatformTime::Seconds();
|
||||
const std::optional<VoxelForgeCore::TileMesh> Mesh =
|
||||
BridgeWorld.generateTile(Key, BridgeTimingOptions, BridgeError);
|
||||
if (!Mesh)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Legacy-bridge unhashed streaming sample failed: %s"),
|
||||
UTF8_TO_TCHAR(BridgeError.c_str()));
|
||||
return false;
|
||||
}
|
||||
const double TileGenerationSeconds = FPlatformTime::Seconds() - TileGenerationStart;
|
||||
BridgeSurfaceTileSamples.push_back({
|
||||
static_cast<int32>(Key.lod), ClassifyAssemblerTile(*Mesh), TileGenerationSeconds,
|
||||
0.0, Mesh->diagnostics.classificationMilliseconds,
|
||||
Mesh->diagnostics.columnComputeMilliseconds, Mesh->diagnostics.meshingMilliseconds,
|
||||
Mesh->diagnostics.heightfieldBandLimited});
|
||||
}
|
||||
|
||||
const auto AssignModelReadyTimes = [](std::vector<FAssemblerTileTimingSample>& Samples)
|
||||
{
|
||||
constexpr int32 WorkerSlots = 16;
|
||||
std::array<double, WorkerSlots> WorkerReadySeconds{};
|
||||
for (FAssemblerTileTimingSample& Sample : Samples)
|
||||
{
|
||||
int32 EarliestWorker = 0;
|
||||
for (int32 Worker = 1; Worker < WorkerSlots; ++Worker)
|
||||
{
|
||||
if (WorkerReadySeconds[Worker] < WorkerReadySeconds[EarliestWorker])
|
||||
{
|
||||
EarliestWorker = Worker;
|
||||
}
|
||||
}
|
||||
Sample.ModelReadySeconds = WorkerReadySeconds[EarliestWorker] + Sample.ServiceSeconds;
|
||||
WorkerReadySeconds[EarliestWorker] = Sample.ModelReadySeconds;
|
||||
}
|
||||
};
|
||||
const auto Percentile = [](std::vector<double> Values, double Fraction)
|
||||
{
|
||||
if (Values.empty()) return 0.0;
|
||||
std::sort(Values.begin(), Values.end());
|
||||
const std::size_t Index = static_cast<std::size_t>(FMath::Clamp(
|
||||
FMath::CeilToInt(Fraction * static_cast<double>(Values.size() - 1u)),
|
||||
0, static_cast<int32>(Values.size() - 1u)));
|
||||
return Values[Index];
|
||||
};
|
||||
const auto EmitKindTiming = [&](const TCHAR* Backend,
|
||||
std::vector<FAssemblerTileTimingSample>& Samples)
|
||||
{
|
||||
AssignModelReadyTimes(Samples);
|
||||
for (int32 LOD = 0; LOD <= 1; ++LOD)
|
||||
{
|
||||
for (int32 KindIndex = 0; KindIndex < 4; ++KindIndex)
|
||||
{
|
||||
const EAssemblerTileKind Kind = static_cast<EAssemblerTileKind>(KindIndex);
|
||||
std::vector<double> ServiceMs, ModelReadySeconds, ClassificationMs, ColumnMs, MeshingMs;
|
||||
int32 HeightfieldBandedTiles = 0;
|
||||
for (const FAssemblerTileTimingSample& Sample : Samples)
|
||||
{
|
||||
if (Sample.LOD != LOD || Sample.Kind != Kind) continue;
|
||||
ServiceMs.push_back(Sample.ServiceSeconds * 1000.0);
|
||||
ModelReadySeconds.push_back(Sample.ModelReadySeconds);
|
||||
ClassificationMs.push_back(Sample.ClassificationMilliseconds);
|
||||
ColumnMs.push_back(Sample.ColumnMilliseconds);
|
||||
MeshingMs.push_back(Sample.MeshingMilliseconds);
|
||||
if(Sample.bHeightfieldBandLimited)++HeightfieldBandedTiles;
|
||||
}
|
||||
const double ReadyP95 = Percentile(ModelReadySeconds, 0.95);
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[WorldAssemblerPawnLag] sample=unhashed_runtime_fastpath backend=%s lod=%d kind=%s tiles=%d heightfield_banded=%d "
|
||||
"service_ms[p50=%.3f p95=%.3f] worker_stage_ms_p95[classify=%.3f column=%.3f mesh=%.3f] "
|
||||
"model_workers=16 model_ready_p95_s=%.6f walk_lag_p95_m=%.3f sprint_lag_p95_m=%.3f "
|
||||
"worker_order=desired_distance"),
|
||||
Backend, LOD, AssemblerTileKindName(Kind), static_cast<int32>(ServiceMs.size()),
|
||||
HeightfieldBandedTiles,
|
||||
Percentile(ServiceMs, 0.50), Percentile(ServiceMs, 0.95),
|
||||
Percentile(ClassificationMs, 0.95), Percentile(ColumnMs, 0.95),
|
||||
Percentile(MeshingMs, 0.95), ReadyP95,
|
||||
ReadyP95 * static_cast<double>(StagedWalkSpeedCmPerSecond) / 100.0,
|
||||
ReadyP95 * static_cast<double>(StagedSprintSpeedCmPerSecond) / 100.0);
|
||||
}
|
||||
}
|
||||
};
|
||||
EmitKindTiming(TEXT("core_native"), NativeSurfaceTileSamples);
|
||||
EmitKindTiming(TEXT("legacy_surface_bridge"), BridgeSurfaceTileSamples);
|
||||
|
||||
const double NativeTotal = NativeSurfaceRegionCompileSeconds
|
||||
+ NativeSurfaceTileGenerationSeconds;
|
||||
const double BridgeTotal = BridgeSurfaceRegionCompileSeconds
|
||||
@@ -14230,6 +14907,91 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
static_cast<unsigned long long>(InitialAggregates[2]),
|
||||
static_cast<unsigned long long>(InitialAggregates[3]));
|
||||
|
||||
if (Actor->ContentManager == nullptr)
|
||||
{
|
||||
OutError = TEXT("Core height agreement proof has no content manager.");
|
||||
return false;
|
||||
}
|
||||
const FVector SurfaceLocalPosition(
|
||||
SurfaceCameraVoxels.x * CoreVoxelSizeCm,
|
||||
SurfaceCameraVoxels.y * CoreVoxelSizeCm,
|
||||
SurfaceCameraVoxels.z * CoreVoxelSizeCm);
|
||||
const FVector SurfaceWorldPosition = Actor->GetActorTransform().TransformPosition(SurfaceLocalPosition);
|
||||
const int32 LegacyUnscaledStrateIndex = Manager->GetStrateIndex(SurfaceLocalPosition.Z);
|
||||
const int32 ResolvedStrateIndex = Actor->GetStrateAtPosition(SurfaceWorldPosition);
|
||||
float ContentGroundLocalZ = 0.0f;
|
||||
bool bContentHasWater = false;
|
||||
float ContentWaterLocalZ = -FLT_MAX;
|
||||
if (ResolvedStrateIndex != 1
|
||||
|| !Actor->ContentManager->QueryNativeSurfaceAnchors(ResolvedStrateIndex,
|
||||
FVector2D(SurfaceLocalPosition.X, SurfaceLocalPosition.Y),
|
||||
ContentGroundLocalZ, bContentHasWater, ContentWaterLocalZ))
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core height agreement failed: raw-strate=%d resolved-strate=%d content-anchor=missing."),
|
||||
LegacyUnscaledStrateIndex, ResolvedStrateIndex);
|
||||
return false;
|
||||
}
|
||||
const float ManagerLocalZ = Actor->ToStrateManagerLocalZ(SurfaceLocalPosition.Z);
|
||||
float ManagerTopZ = 0.0f, ManagerBottomZ = 0.0f;
|
||||
if (!Manager->GetStrateUnrealZRange(ManagerLocalZ, ManagerTopZ, ManagerBottomZ))
|
||||
{
|
||||
OutError = TEXT("Core height agreement failed: manager has no corrected strate bounds.");
|
||||
return false;
|
||||
}
|
||||
const float AtmosphereTopLocalZ = Actor->FromStrateManagerLocalZ(ManagerTopZ);
|
||||
const float AtmosphereBottomLocalZ = Actor->FromStrateManagerLocalZ(ManagerBottomZ);
|
||||
const FVector GroundQueryLocal(
|
||||
SurfaceLocalPosition.X, SurfaceLocalPosition.Y, ContentGroundLocalZ + CoreVoxelSizeCm);
|
||||
const FVector GroundQueryWorld = Actor->GetActorTransform().TransformPosition(GroundQueryLocal);
|
||||
float TerrainGroundWorldZ = 0.0f, TerrainCeilingWorldZ = 0.0f;
|
||||
if (!Actor->GetVoxelSurfaceHeightAt(GroundQueryWorld, TerrainGroundWorldZ, TerrainCeilingWorldZ))
|
||||
{
|
||||
OutError = TEXT("Core height agreement failed: native terrain returned no ground anchor.");
|
||||
return false;
|
||||
}
|
||||
const float ContentGroundWorldZ = Actor->GetActorTransform().TransformPosition(
|
||||
FVector(SurfaceLocalPosition.X, SurfaceLocalPosition.Y, ContentGroundLocalZ)).Z;
|
||||
const float GroundAgreementCm = FMath::Abs(TerrainGroundWorldZ - ContentGroundWorldZ);
|
||||
if (GroundAgreementCm > 25.0f
|
||||
|| ContentGroundLocalZ < AtmosphereBottomLocalZ
|
||||
|| ContentGroundLocalZ > AtmosphereTopLocalZ
|
||||
|| (bContentHasWater && (ContentWaterLocalZ < AtmosphereBottomLocalZ
|
||||
|| ContentWaterLocalZ > AtmosphereTopLocalZ)))
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Core height agreement failed: terrain/content delta=%.3f cm ground=%.3f water=%.3f atmosphere=(%.3f,%.3f)."),
|
||||
GroundAgreementCm, ContentGroundLocalZ, ContentWaterLocalZ,
|
||||
AtmosphereBottomLocalZ, AtmosphereTopLocalZ);
|
||||
return false;
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldTest] height_agreement=PASS legacy_unscaled_strate=%d corrected_strate=%d terrain_ground_cm=%.3f content_ground_cm=%.3f water_enabled=%d water_cm=%.3f atmosphere_bounds_cm=(%.3f,%.3f) ground_delta_cm=%.3f"),
|
||||
LegacyUnscaledStrateIndex, ResolvedStrateIndex, TerrainGroundWorldZ,
|
||||
ContentGroundLocalZ, bContentHasWater ? 1 : 0, ContentWaterLocalZ,
|
||||
AtmosphereBottomLocalZ, AtmosphereTopLocalZ, GroundAgreementCm);
|
||||
if (!Actor->bUseVoxelForgeCore)
|
||||
{
|
||||
OutError = TEXT("BuildHost height agreement fixture unexpectedly has Core disabled.");
|
||||
return false;
|
||||
}
|
||||
Actor->bUseVoxelForgeCore = false;
|
||||
float DisabledGroundLocalZ = 0.0f;
|
||||
bool bDisabledHasWater = false;
|
||||
float DisabledWaterLocalZ = -FLT_MAX;
|
||||
const bool bNativeAnchorEscapedSwitch = Actor->ContentManager->QueryNativeSurfaceAnchors(
|
||||
ResolvedStrateIndex, FVector2D(SurfaceLocalPosition.X, SurfaceLocalPosition.Y),
|
||||
DisabledGroundLocalZ, bDisabledHasWater, DisabledWaterLocalZ);
|
||||
Actor->bUseVoxelForgeCore = true;
|
||||
if (bNativeAnchorEscapedSwitch)
|
||||
{
|
||||
OutError = TEXT("BuildHost native content anchor remained available with Core disabled.");
|
||||
return false;
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldTest] switch_off_content_gate=PASS native_anchor_when_disabled=0 legacy_voxel_cm=%.1f"),
|
||||
VOXEL_SIZE);
|
||||
|
||||
AActor* Anchor = TestWorld->SpawnActor<AActor>(AActor::StaticClass());
|
||||
if (Anchor == nullptr)
|
||||
{
|
||||
@@ -14283,8 +15045,38 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldTest] pass phase=anchor old=(20,0,0) new=(21,0,0) requested=1 released=1"));
|
||||
|
||||
Actor->CarveAtPosition(Actor->GetActorTransform().TransformPosition(CarveLocalPositionCm),
|
||||
CarveBrushRadiusLegacyVoxels, 40.0f);
|
||||
const FVector CarveWorldPosition = Actor->GetActorTransform().TransformPosition(CarveLocalPositionCm);
|
||||
const FVector InsideBrushWorldPosition = CarveWorldPosition + FVector(60.0f, 0.0f, 0.0f);
|
||||
const FVector OutsideBrushWorldPosition = CarveWorldPosition + FVector(80.0f, 0.0f, 0.0f);
|
||||
float CarveDensityBefore = 0.0f, InsideDensityBefore = 0.0f, OutsideDensityBefore = 0.0f;
|
||||
if (!Actor->QueryVoxelForgeCoreDensityAt(CarveWorldPosition, CarveDensityBefore)
|
||||
|| !Actor->QueryVoxelForgeCoreDensityAt(InsideBrushWorldPosition, InsideDensityBefore)
|
||||
|| !Actor->QueryVoxelForgeCoreDensityAt(OutsideBrushWorldPosition, OutsideDensityBefore))
|
||||
{
|
||||
OutError = TEXT("Core Blueprint carve fixture could not read its pre-edit density samples.");
|
||||
return false;
|
||||
}
|
||||
Actor->CarveAtPosition(CarveWorldPosition, CarveBrushRadiusLegacyVoxels, 10.0f);
|
||||
float CarveDensityAfter = 0.0f, InsideDensityAfter = 0.0f, OutsideDensityAfter = 0.0f;
|
||||
if (!Actor->QueryVoxelForgeCoreDensityAt(CarveWorldPosition, CarveDensityAfter)
|
||||
|| !Actor->QueryVoxelForgeCoreDensityAt(InsideBrushWorldPosition, InsideDensityAfter)
|
||||
|| !Actor->QueryVoxelForgeCoreDensityAt(OutsideBrushWorldPosition, OutsideDensityAfter))
|
||||
{
|
||||
OutError = TEXT("Core Blueprint carve fixture could not read its post-edit density samples.");
|
||||
return false;
|
||||
}
|
||||
const float CarveCenterDelta = CarveDensityAfter - CarveDensityBefore;
|
||||
const float CarveInsideDelta = InsideDensityAfter - InsideDensityBefore;
|
||||
const float CarveOutsideDelta = OutsideDensityAfter - OutsideDensityBefore;
|
||||
if (!FMath::IsNearlyEqual(CarveCenterDelta, 5.0f, 0.01f)
|
||||
|| !(CarveInsideDelta > 0.0f && CarveInsideDelta < CarveCenterDelta)
|
||||
|| !FMath::IsNearlyZero(CarveOutsideDelta, 0.001f))
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Blueprint carve sign/size failed: center=%.4f inside60cm=%.4f outside80cm=%.4f; expected +5, partial +, zero."),
|
||||
CarveCenterDelta, CarveInsideDelta, CarveOutsideDelta);
|
||||
return false;
|
||||
}
|
||||
if (Actor->LastVoxelForgeCoreEditSequence != 1
|
||||
|| Actor->LastVoxelForgeCoreEditDigest != ExpectedEditDigest
|
||||
|| Actor->LastVoxelForgeCoreAffectedTiles != ExpectedAffectedTiles)
|
||||
@@ -14319,6 +15111,9 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
ExpectedAffectedTiles.Num(), CarveTargetTile.Coord.X, CarveTargetTile.Coord.Y,
|
||||
CarveTargetTile.Coord.Z, CarveTargetTile.Level,
|
||||
static_cast<unsigned long long>(ExpectedPostEditHashes.FindChecked(CarveTargetTile).Mesh));
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldTest] blueprint_carve=PASS radius_legacy=6 strength_legacy=10 center_delta=%.4f inside_60cm_delta=%.4f outside_80cm_delta=%.4f"),
|
||||
CarveCenterDelta, CarveInsideDelta, CarveOutsideDelta);
|
||||
|
||||
Actor->UnregisterStreamingAnchor(Anchor);
|
||||
Anchor->Destroy();
|
||||
@@ -14427,11 +15222,11 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
static_cast<unsigned long long>(TravelAggregates[2]),
|
||||
static_cast<unsigned long long>(TravelAggregates[3]));
|
||||
|
||||
const FVector SurfaceWorldPosition(
|
||||
const FVector SurfaceTravelWorldPosition(
|
||||
SurfaceCameraVoxels.x * CoreVoxelSizeCm,
|
||||
SurfaceCameraVoxels.y * CoreVoxelSizeCm,
|
||||
SurfaceCameraVoxels.z * CoreVoxelSizeCm);
|
||||
TravelPawn->SetActorLocation(SurfaceWorldPosition);
|
||||
TravelPawn->SetActorLocation(SurfaceTravelWorldPosition);
|
||||
auto WaitForSurfaceVisualTiles = [&]()
|
||||
{
|
||||
const double Deadline = FPlatformTime::Seconds() + 120.0;
|
||||
@@ -14612,6 +15407,40 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
static_cast<unsigned long long>(SurfaceTravelAggregates[1]),
|
||||
static_cast<unsigned long long>(SurfaceTravelAggregates[2]),
|
||||
static_cast<unsigned long long>(SurfaceTravelAggregates[3]));
|
||||
const FVector FillInsideWorldPosition = CarveWorldPosition + FVector(60.0f, 0.0f, 0.0f);
|
||||
const FVector FillOutsideWorldPosition = CarveWorldPosition + FVector(80.0f, 0.0f, 0.0f);
|
||||
float FillDensityBefore = 0.0f, FillInsideDensityBefore = 0.0f, FillOutsideDensityBefore = 0.0f;
|
||||
if (!Actor->QueryVoxelForgeCoreDensityAt(CarveWorldPosition, FillDensityBefore)
|
||||
|| !Actor->QueryVoxelForgeCoreDensityAt(FillInsideWorldPosition, FillInsideDensityBefore)
|
||||
|| !Actor->QueryVoxelForgeCoreDensityAt(FillOutsideWorldPosition, FillOutsideDensityBefore))
|
||||
{
|
||||
OutError = TEXT("Core Blueprint fill fixture could not read its pre-edit size samples.");
|
||||
return false;
|
||||
}
|
||||
Actor->FillAtPosition(CarveWorldPosition, 6.0f, 10.0f);
|
||||
float FillDensityAfter = 0.0f, FillInsideDensityAfter = 0.0f, FillOutsideDensityAfter = 0.0f;
|
||||
if (!Actor->QueryVoxelForgeCoreDensityAt(CarveWorldPosition, FillDensityAfter)
|
||||
|| !Actor->QueryVoxelForgeCoreDensityAt(FillInsideWorldPosition, FillInsideDensityAfter)
|
||||
|| !Actor->QueryVoxelForgeCoreDensityAt(FillOutsideWorldPosition, FillOutsideDensityAfter))
|
||||
{
|
||||
OutError = TEXT("Core Blueprint fill fixture could not read its post-edit size samples.");
|
||||
return false;
|
||||
}
|
||||
const float FillCenterDelta = FillDensityAfter - FillDensityBefore;
|
||||
const float FillInsideDelta = FillInsideDensityAfter - FillInsideDensityBefore;
|
||||
const float FillOutsideDelta = FillOutsideDensityAfter - FillOutsideDensityBefore;
|
||||
if (!FMath::IsNearlyEqual(FillCenterDelta, -5.0f, 0.01f)
|
||||
|| !(FillInsideDelta < 0.0f && FillInsideDelta > FillCenterDelta)
|
||||
|| !FMath::IsNearlyZero(FillOutsideDelta, 0.001f))
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Blueprint fill sign/size failed: center=%.4f inside60cm=%.4f outside80cm=%.4f; expected -5, partial -, zero."),
|
||||
FillCenterDelta, FillInsideDelta, FillOutsideDelta);
|
||||
return false;
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldTest] blueprint_fill=PASS radius_legacy=6 strength_legacy=10 center_delta=%.4f inside_60cm_delta=%.4f outside_80cm_delta=%.4f"),
|
||||
FillCenterDelta, FillInsideDelta, FillOutsideDelta);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user