standalone: far-survival audit + LOD8 default with explicit far shells; neighbourhood-first viewer
- FarSurvivalAudit: checks every stratum top/bottom boundary (4x4 columns per far tile) and every in-view far connector (sections <=128 voxels, 4 radial rays) against the emitted mesh. Falsified: removing one tile's shell triangles -> 16 missing, 16 holes, exit 14. Passes at LOD6/7/8. - Far connectors follow source radial crossings; strata patches emitted at LOD7/8. Default LOD8. - Viewer: separate near ShapeWorld for the 27 player tiles, full world built on its own thread; 27/27 tiles hash-identical to the direct build. W1/W8 60 hash fields equal; routes 5642/5642. Cost, measured by me head to head (8 workers, cold, BelowNormal 0xFF00): LOD8 without the shells 1.17-1.25 s, LOD8 with the shells 1.80 s; payload 92.6 -> 276.7 MiB. The shells are far too expensive, and the pipelined benchmark whole view is 2.92 s (shape compiled twice): both to fix. 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
5602a281b9
commit
a40b0454b3
7 files changed
+1350
-17
No files matched your search
@@ -21,6 +21,7 @@
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
// Standalone-only world compiler. It compiles the runtime room/tunnel/passages into
|
||||
@@ -30,6 +31,12 @@ class FStandaloneShapeWorld
|
||||
{
|
||||
public:
|
||||
enum class EDetailVariant : uint8 { Coherent, Smooth, PerVoxel };
|
||||
struct FFarConnectorSegment
|
||||
{
|
||||
FVector Start=FVector::ZeroVector;
|
||||
FVector End=FVector::ZeroVector;
|
||||
float Radius=0.0f;
|
||||
};
|
||||
struct FEditBrush
|
||||
{
|
||||
enum class EShape : uint8 { Sphere, Capsule, RoundedBox };
|
||||
@@ -393,6 +400,64 @@ public:
|
||||
const FBuildStats& GetStats() const { return Stats; }
|
||||
bool IsBuilt() const { return Built; }
|
||||
bool HasFarWorld() const { return FarBuilt; }
|
||||
const std::vector<FFarConnectorSegment>& GetFarConnectorSegments() const
|
||||
{ return FarConnectorSegments; }
|
||||
|
||||
size_t BuildFarConnectorAirSamples(const FIntVector& TileOrigin,int32 Step,int32 Cells,
|
||||
std::unordered_set<uint64>& OutSamples) const
|
||||
{
|
||||
OutSamples.clear();
|
||||
if(FarConnectorSegments.empty()||Step<=0||Cells<=0)return 0;
|
||||
const double Pad=Step;
|
||||
const double TileMin[3]={TileOrigin.X-Pad,TileOrigin.Y-Pad,TileOrigin.Z-Pad};
|
||||
const double TileMax[3]={TileOrigin.X+(Cells+1)*Step,TileOrigin.Y+(Cells+1)*Step,
|
||||
TileOrigin.Z+(Cells+1)*Step};
|
||||
for(const FFarConnectorSegment& Connector:FarConnectorSegments)
|
||||
{
|
||||
const double Start[3]={Connector.Start.X,Connector.Start.Y,Connector.Start.Z};
|
||||
const double End[3]={Connector.End.X,Connector.End.Y,Connector.End.Z};
|
||||
bool bOverlaps=true;
|
||||
for(int32 Axis=0;Axis<3;++Axis)
|
||||
bOverlaps &= std::min(Start[Axis],End[Axis])-Connector.Radius<=TileMax[Axis]
|
||||
&&std::max(Start[Axis],End[Axis])+Connector.Radius>=TileMin[Axis];
|
||||
if(!bOverlaps)continue;
|
||||
const double Delta[3]={End[0]-Start[0],End[1]-Start[1],End[2]-Start[2]};
|
||||
const double Length=std::sqrt(Delta[0]*Delta[0]+Delta[1]*Delta[1]+Delta[2]*Delta[2]);
|
||||
if(Length<=1.0e-6)continue;
|
||||
const int32 Samples=std::max(1,static_cast<int32>(std::ceil(Length/(Step*0.25))));
|
||||
for(int32 I=0;I<=Samples;++I)
|
||||
{
|
||||
const double T=static_cast<double>(I)/Samples;
|
||||
const double P[3]={Start[0]+Delta[0]*T,Start[1]+Delta[1]*T,Start[2]+Delta[2]*T};
|
||||
if(P[0]<TileMin[0]||P[0]>TileMax[0]||P[1]<TileMin[1]||P[1]>TileMax[1]
|
||||
||P[2]<TileMin[2]||P[2]>TileMax[2])continue;
|
||||
int32 Base[3];
|
||||
for(int32 Axis=0;Axis<3;++Axis)
|
||||
Base[Axis]=static_cast<int32>(std::floor((P[Axis]-(&TileOrigin.X)[Axis])/Step));
|
||||
for(int32 DZ=0;DZ<=1;++DZ)for(int32 DY=0;DY<=1;++DY)for(int32 DX=0;DX<=1;++DX)
|
||||
{
|
||||
const int32 IX=Base[0]+DX,IY=Base[1]+DY,IZ=Base[2]+DZ;
|
||||
if(IX<0||IY<0||IZ<0||IX>Cells||IY>Cells||IZ>Cells)continue;
|
||||
OutSamples.insert(PackLatticeKey(TileOrigin.X+IX*Step,
|
||||
TileOrigin.Y+IY*Step,TileOrigin.Z+IZ*Step));
|
||||
}
|
||||
}
|
||||
}
|
||||
return OutSamples.size();
|
||||
}
|
||||
|
||||
class FScopedForcedFarAirSamples
|
||||
{
|
||||
public:
|
||||
explicit FScopedForcedFarAirSamples(const std::unordered_set<uint64>* AirSamples)
|
||||
:PreviousAir(ForcedFarAirSamples)
|
||||
{ ForcedFarAirSamples=AirSamples; }
|
||||
~FScopedForcedFarAirSamples(){ForcedFarAirSamples=PreviousAir;}
|
||||
FScopedForcedFarAirSamples(const FScopedForcedFarAirSamples&)=delete;
|
||||
FScopedForcedFarAirSamples& operator=(const FScopedForcedFarAirSamples&)=delete;
|
||||
private:
|
||||
const std::unordered_set<uint64>* PreviousAir=nullptr;
|
||||
};
|
||||
bool DebugCriticalAirAt(double X,double Y,double Z) const { return IsInsideCriticalAir(X,Y,Z); }
|
||||
bool DebugSolidOverlayAt(double X,double Y,double Z) const { return IsInsideSolidOverlay(X,Y,Z); }
|
||||
|
||||
@@ -451,6 +516,7 @@ public:
|
||||
{
|
||||
int32 IX=0,IY=0,IZ=0;
|
||||
const bool bInteger=NearInteger(X,IX)&&NearInteger(Y,IY)&&NearInteger(Z,IZ);
|
||||
if(bInteger&&IsForcedFarAirSample(IX,IY,IZ))return 0.05f;
|
||||
const bool bPacked=bInteger&&Contains(IX,IY,IZ);
|
||||
if(bPacked)
|
||||
return PackedDensityAt(IX,IY,IZ,IsRock(IX,IY,IZ),X,Y,Z);
|
||||
@@ -459,6 +525,7 @@ public:
|
||||
|
||||
float DensityAtLattice(int32 X,int32 Y,int32 Z) const
|
||||
{
|
||||
if(IsForcedFarAirSample(X,Y,Z))return 0.05f;
|
||||
const bool bPacked=Contains(X,Y,Z);
|
||||
if(bPacked)
|
||||
return PackedDensityAt(X,Y,Z,IsRock(X,Y,Z),static_cast<float>(X),
|
||||
@@ -468,6 +535,18 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
static uint64 PackLatticeKey(int32 X,int32 Y,int32 Z)
|
||||
{
|
||||
constexpr uint64 Mask=0x1FFFFFull;
|
||||
return (static_cast<uint64>(static_cast<uint32>(X)&Mask)<<42)
|
||||
|(static_cast<uint64>(static_cast<uint32>(Y)&Mask)<<21)
|
||||
|(static_cast<uint64>(static_cast<uint32>(Z)&Mask));
|
||||
}
|
||||
static bool IsForcedFarAirSample(int32 X,int32 Y,int32 Z)
|
||||
{
|
||||
const auto* Samples=ForcedFarAirSamples;
|
||||
return Samples&&Samples->contains(PackLatticeKey(X,Y,Z));
|
||||
}
|
||||
float PackedDensityAt(int32 X,int32 Y,int32 Z,bool bRock,float FX,float FY,float FZ) const
|
||||
{
|
||||
const size_t Column=static_cast<size_t>(Y-Min[1])*Size[0]+static_cast<size_t>(X-Min[0]);
|
||||
@@ -1970,6 +2049,7 @@ private:
|
||||
void BuildFarParentShapes()
|
||||
{
|
||||
constexpr int32 ParentPitch=2048;
|
||||
FarConnectorSegments.clear();
|
||||
struct FParent { int32 GX=0,GY=0,Strate=0; FVector Center; double Rx=0,Ry=0,Rz=0; };
|
||||
const int32 GX0=FloorDiv(ClipMin[0],ParentPitch)-1,GY0=FloorDiv(ClipMin[1],ParentPitch)-1;
|
||||
const int32 GX1=FloorDiv(ClipMax[0],ParentPitch)+1,GY1=FloorDiv(ClipMax[1],ParentPitch)+1;
|
||||
@@ -2024,6 +2104,7 @@ private:
|
||||
if(MaxX>=Min[0]&&MinX<=Max[0]&&MaxY>=Min[1]&&MinY<=Max[1])continue;
|
||||
AddTaperedCapsule(A,B,64.0f,64.0f,true);
|
||||
Primitives.back().BlendRadius=0.0;
|
||||
FarConnectorSegments.push_back({A,B,64.0f});
|
||||
}
|
||||
}
|
||||
if(!Parents.empty())
|
||||
@@ -3154,6 +3235,7 @@ private:
|
||||
std::vector<FInterval> LayerRanges;
|
||||
std::vector<FLayerField> LayerFields;
|
||||
std::vector<FPrimitive> Primitives;
|
||||
std::vector<FFarConnectorSegment> FarConnectorSegments;
|
||||
std::vector<FEditBrush> EditBrushes;
|
||||
std::array<FAxisBins,3> AxisBins;
|
||||
std::unordered_map<FBinKey,FBinItems,FBinHash> SpatialBins;
|
||||
@@ -3164,6 +3246,7 @@ private:
|
||||
std::array<std::vector<uint64>,3> CandidateAxisWords;
|
||||
std::vector<uint64> CandidateWords;
|
||||
FBuildStats Stats;
|
||||
inline static thread_local const std::unordered_set<uint64>* ForcedFarAirSamples=nullptr;
|
||||
};
|
||||
|
||||
namespace FStandaloneShapeWorldAccess
|
||||
|
||||
Reference in new issue
Block a user