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
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "WorldMesher.h"
|
||||
#include "StandaloneShapeWorld.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <algorithm>
|
||||
@@ -152,6 +153,277 @@ inline uint64 HashStandaloneCpuTileMesh(const FStandaloneCpuTile& Tile)
|
||||
return Hash;
|
||||
}
|
||||
|
||||
inline double FindFarConnectorSurfaceRadius(const FStandaloneShapeWorld& Shape,const FVector& Center,
|
||||
const FVector& Direction,double PreferredRadius)
|
||||
{
|
||||
const double SearchLength=PreferredRadius*2.0+32.0;
|
||||
const double AcceptBand=std::max(16.0,PreferredRadius*0.75);
|
||||
constexpr double Stride=8.0;
|
||||
double PreviousDistance=0.0;
|
||||
bool PreviousSolid=Shape.DensityAt(static_cast<float>(Center.X),static_cast<float>(Center.Y),
|
||||
static_cast<float>(Center.Z))<0.0f;
|
||||
double BestRadius=PreferredRadius,BestDifference=std::numeric_limits<double>::infinity();
|
||||
for(double Distance=Stride;Distance<=SearchLength+1.0e-6;Distance=std::min(Distance+Stride,SearchLength))
|
||||
{
|
||||
const FVector P=Center+Direction*Distance;
|
||||
const bool Solid=Shape.DensityAt(static_cast<float>(P.X),static_cast<float>(P.Y),
|
||||
static_cast<float>(P.Z))<0.0f;
|
||||
if(Solid!=PreviousSolid)
|
||||
{
|
||||
double Lo=PreviousDistance,Hi=Distance;
|
||||
for(int32 I=0;I<6;++I)
|
||||
{
|
||||
const double Mid=(Lo+Hi)*0.5;
|
||||
const FVector M=Center+Direction*Mid;
|
||||
const bool MidSolid=Shape.DensityAt(static_cast<float>(M.X),static_cast<float>(M.Y),
|
||||
static_cast<float>(M.Z))<0.0f;
|
||||
if(MidSolid==PreviousSolid)Lo=Mid;else Hi=Mid;
|
||||
}
|
||||
const double Crossing=(Lo+Hi)*0.5;
|
||||
const double Difference=std::abs(Crossing-PreferredRadius);
|
||||
if(Difference<=AcceptBand&&Difference<BestDifference)
|
||||
{BestRadius=Crossing;BestDifference=Difference;}
|
||||
}
|
||||
if(Distance>=SearchLength-1.0e-6)break;
|
||||
PreviousDistance=Distance;PreviousSolid=Solid;
|
||||
}
|
||||
return BestRadius;
|
||||
}
|
||||
|
||||
inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FVoxelTileKey& Key,
|
||||
int32 Step,FVoxelMeshData& Mesh)
|
||||
{
|
||||
if(Key.Level<6||Step<=0)return;
|
||||
constexpr int32 SideCount=8;
|
||||
const FIntVector TileOrigin=Key.OriginVoxels();
|
||||
const int32 TileExtent=Key.ExtentVoxels();
|
||||
std::vector<FVector> NewVertices;
|
||||
std::vector<FVector> NewNormals;
|
||||
std::vector<FVector2f> NewUVs;
|
||||
std::vector<FColor> NewColors;
|
||||
std::vector<int32> NewTriangles;
|
||||
const int32 OldVertexCount=Mesh.Vertices.Num();
|
||||
const bool bHasUVs=Mesh.UVs.Num()==OldVertexCount;
|
||||
const bool bHasColors=Mesh.Colors.Num()==OldVertexCount;
|
||||
const FColor Color=bHasColors&&OldVertexCount>0?Mesh.Colors[0]:FColor(160,160,160,255);
|
||||
for(const auto& Connector:Shape.GetFarConnectorSegments())
|
||||
{
|
||||
const FVector Delta=Connector.End-Connector.Start;
|
||||
const double Length=std::sqrt(Delta.X*Delta.X+Delta.Y*Delta.Y+Delta.Z*Delta.Z);
|
||||
if(Length<=1.0e-6)continue;
|
||||
const double Starts[3]={Connector.Start.X,Connector.Start.Y,Connector.Start.Z};
|
||||
const double Deltas[3]={Delta.X,Delta.Y,Delta.Z};
|
||||
const double Low[3]={TileOrigin.X-Connector.Radius,TileOrigin.Y-Connector.Radius,
|
||||
TileOrigin.Z-Connector.Radius};
|
||||
const double High[3]={TileOrigin.X+TileExtent+Connector.Radius,
|
||||
TileOrigin.Y+TileExtent+Connector.Radius,TileOrigin.Z+TileExtent+Connector.Radius};
|
||||
double TMin=0.0,TMax=1.0;bool bIntersects=true;
|
||||
for(int32 AxisIndex=0;AxisIndex<3;++AxisIndex)
|
||||
{
|
||||
if(std::abs(Deltas[AxisIndex])<1.0e-9)
|
||||
{if(Starts[AxisIndex]<Low[AxisIndex]||Starts[AxisIndex]>High[AxisIndex])bIntersects=false;continue;}
|
||||
double A=(Low[AxisIndex]-Starts[AxisIndex])/Deltas[AxisIndex];
|
||||
double B=(High[AxisIndex]-Starts[AxisIndex])/Deltas[AxisIndex];
|
||||
if(A>B)std::swap(A,B);
|
||||
TMin=std::max(TMin,A);TMax=std::min(TMax,B);
|
||||
if(TMin>TMax){bIntersects=false;break;}
|
||||
}
|
||||
if(!bIntersects||TMax-TMin<1.0e-6)continue;
|
||||
const FVector Axis=Delta/Length;
|
||||
FVector U=FVector::CrossProduct(Axis,FVector::UpVector);
|
||||
if(U.SizeSquared()<0.01)U=FVector::CrossProduct(Axis,FVector::RightVector);
|
||||
U.Normalize();
|
||||
FVector V=FVector::CrossProduct(Axis,U);V.Normalize();
|
||||
const int32 AxialSampleCount=std::max(1,static_cast<int32>(std::ceil(Length/128.0)));
|
||||
std::vector<double> RingParameters{TMin};
|
||||
for(int32 Sample=0;Sample<AxialSampleCount;++Sample)
|
||||
{
|
||||
const double T=(static_cast<double>(Sample)+0.5)/AxialSampleCount;
|
||||
if(T>TMin+1.0e-6&&T<TMax-1.0e-6)RingParameters.push_back(T);
|
||||
}
|
||||
RingParameters.push_back(TMax);
|
||||
const int32 RingCount=static_cast<int32>(RingParameters.size())-1;
|
||||
const int32 VertexBase=static_cast<int32>(NewVertices.size());
|
||||
for(int32 Ring=0;Ring<=RingCount;++Ring)
|
||||
{
|
||||
const double T=RingParameters[static_cast<size_t>(Ring)];
|
||||
const FVector Center=Connector.Start+Delta*T;
|
||||
for(int32 Side=0;Side<SideCount;++Side)
|
||||
{
|
||||
const double Angle=2.0*PI*Side/SideCount;
|
||||
FVector Radial=U*std::cos(Angle)+V*std::sin(Angle);Radial.Normalize();
|
||||
const double SourceRadius=FindFarConnectorSurfaceRadius(Shape,Center,Radial,
|
||||
Connector.Radius);
|
||||
const FVector P=Center+Radial*SourceRadius;
|
||||
NewVertices.emplace_back(P.X*25.0,P.Y*25.0,P.Z*25.0);
|
||||
NewNormals.emplace_back(-Radial.X,-Radial.Y,-Radial.Z);
|
||||
if(bHasUVs)NewUVs.emplace_back(static_cast<float>(T*Length/256.0),
|
||||
static_cast<float>(Side)/SideCount);
|
||||
if(bHasColors)NewColors.push_back(Color);
|
||||
}
|
||||
}
|
||||
for(int32 Ring=0;Ring<RingCount;++Ring)
|
||||
for(int32 Side=0;Side<SideCount;++Side)
|
||||
{
|
||||
const int32 Next=(Side+1)%SideCount;
|
||||
const int32 A=VertexBase+Ring*SideCount+Side;
|
||||
const int32 B=VertexBase+Ring*SideCount+Next;
|
||||
const int32 C=VertexBase+(Ring+1)*SideCount+Side;
|
||||
const int32 D=VertexBase+(Ring+1)*SideCount+Next;
|
||||
// The far links are carved passages, so their visible normals face inward.
|
||||
NewTriangles.insert(NewTriangles.end(),{A,C,B,B,C,D});
|
||||
}
|
||||
}
|
||||
if(NewVertices.empty()||NewTriangles.empty())return;
|
||||
|
||||
const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,OldVertexCount);
|
||||
const int32 AddedVertices=static_cast<int32>(NewVertices.size());
|
||||
auto InsertVertices=[&](auto& Existing,const auto& Added)
|
||||
{
|
||||
using TArrayType=std::remove_reference_t<decltype(Existing)>;
|
||||
TArrayType Combined;Combined.Reserve(Existing.Num()+static_cast<int32>(Added.size()));
|
||||
for(int32 I=0;I<OldSurfaceVertices;++I)Combined.Add(Existing[I]);
|
||||
for(const auto& Value:Added)Combined.Add(Value);
|
||||
for(int32 I=OldSurfaceVertices;I<Existing.Num();++I)Combined.Add(Existing[I]);
|
||||
Existing=std::move(Combined);
|
||||
};
|
||||
InsertVertices(Mesh.Vertices,NewVertices);
|
||||
InsertVertices(Mesh.Normals,NewNormals);
|
||||
if(bHasUVs)InsertVertices(Mesh.UVs,NewUVs);
|
||||
if(bHasColors)InsertVertices(Mesh.Colors,NewColors);
|
||||
for(int32& Index:Mesh.Triangles)if(Index>=OldSurfaceVertices)Index+=AddedVertices;
|
||||
const int32 CeilingStart=std::max(0,Mesh.Triangles.Num()-Mesh.NumCeilingTriangles*3);
|
||||
TArray<int32> CombinedTriangles;CombinedTriangles.Reserve(Mesh.Triangles.Num()+static_cast<int32>(NewTriangles.size()));
|
||||
for(int32 I=0;I<CeilingStart;++I)CombinedTriangles.Add(Mesh.Triangles[I]);
|
||||
for(const int32 Index:NewTriangles)CombinedTriangles.Add(OldSurfaceVertices+Index);
|
||||
for(int32 I=CeilingStart;I<Mesh.Triangles.Num();++I)CombinedTriangles.Add(Mesh.Triangles[I]);
|
||||
Mesh.Triangles=std::move(CombinedTriangles);
|
||||
Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertices;
|
||||
}
|
||||
|
||||
inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FStandaloneShapeWorld& Shape,
|
||||
const FVoxelTileKey& Key,int32 Step,FVoxelMeshData& Mesh)
|
||||
{
|
||||
if(Key.Level<7||Step<4||!World.Manager)return;
|
||||
FStandaloneShapeWorld::FScopedForcedFarAirSamples SourceFieldScope(nullptr);
|
||||
struct FBoundary { double Z=0.0,HalfWidth=8.0; };
|
||||
const auto& Layout=World.Manager->GetLayout();
|
||||
std::vector<FBoundary> Boundaries;
|
||||
for(const FStrateSlot& Slot:Layout)
|
||||
{
|
||||
const FStrateGenerationParams Params=World.Manager->GetGenerationParams(
|
||||
FIntVector(0,0,(Slot.TopChunkZ+Slot.BottomChunkZ)/2));
|
||||
const double HalfWidth=std::max(8.0,static_cast<double>(Params.BoundarySealThickness)+8.0);
|
||||
Boundaries.push_back({static_cast<double>(Slot.BottomChunkZ)*CHUNK_SIZE,HalfWidth});
|
||||
Boundaries.push_back({static_cast<double>(Slot.TopChunkZ+1)*CHUNK_SIZE,HalfWidth});
|
||||
}
|
||||
std::sort(Boundaries.begin(),Boundaries.end(),[](const FBoundary& A,const FBoundary& B){return A.Z<B.Z;});
|
||||
Boundaries.erase(std::unique(Boundaries.begin(),Boundaries.end(),[](const FBoundary& A,const FBoundary& B)
|
||||
{return std::abs(A.Z-B.Z)<0.01;}),Boundaries.end());
|
||||
const FIntVector Origin=Key.OriginVoxels();const int32 Extent=Key.ExtentVoxels();
|
||||
const int32 FineStep=std::max(1,Step/2);const int32 GridCells=Extent/FineStep;
|
||||
if(GridCells<=0||GridCells>256)return;
|
||||
struct FNode { double Z=0.0;FVector Normal{};int32 Index=INDEX_NONE;bool bValid=false,bBelowSolid=false; };
|
||||
std::vector<FVector> NewVertices,NewNormals;
|
||||
std::vector<FVector2f> NewUVs;std::vector<FColor> NewColors;
|
||||
std::vector<int32> NewTriangles;
|
||||
const int32 OldVertexCount=Mesh.Vertices.Num();
|
||||
const bool bHasUVs=Mesh.UVs.Num()==OldVertexCount;
|
||||
const bool bHasColors=Mesh.Colors.Num()==OldVertexCount;
|
||||
const FColor Color=bHasColors&&OldVertexCount>0?Mesh.Colors[0]:FColor(160,160,160,255);
|
||||
const auto Density=[&](double X,double Y,double Z)
|
||||
{return Shape.DensityAt(static_cast<float>(X),static_cast<float>(Y),static_cast<float>(Z));};
|
||||
for(const FBoundary& Boundary:Boundaries)
|
||||
{
|
||||
if(Boundary.Z+Boundary.HalfWidth<Origin.Z||Boundary.Z-Boundary.HalfWidth>Origin.Z+Extent)continue;
|
||||
const int32 ZMin=std::max(Origin.Z,static_cast<int32>(std::floor(Boundary.Z-Boundary.HalfWidth)));
|
||||
const int32 ZMax=std::min(Origin.Z+Extent,static_cast<int32>(std::ceil(Boundary.Z+Boundary.HalfWidth)));
|
||||
std::vector<FNode> Nodes(static_cast<size_t>(GridCells+1)*(GridCells+1));
|
||||
for(int32 YI=0;YI<=GridCells;++YI)for(int32 XI=0;XI<=GridCells;++XI)
|
||||
{
|
||||
const double X=Origin.X+XI*FineStep,Y=Origin.Y+YI*FineStep;
|
||||
int32 PreviousZ=ZMin;bool PreviousSolid=Density(X,Y,PreviousZ)<0.0f;
|
||||
FNode& Node=Nodes[static_cast<size_t>(YI)*(GridCells+1)+XI];
|
||||
for(int32 Z=ZMin+1;Z<=ZMax;++Z)
|
||||
{
|
||||
const bool Solid=Density(X,Y,Z)<0.0f;
|
||||
if(Solid==PreviousSolid){PreviousZ=Z;PreviousSolid=Solid;continue;}
|
||||
double Lo=PreviousZ,Hi=Z;
|
||||
for(int32 I=0;I<8;++I)
|
||||
{
|
||||
const double Mid=(Lo+Hi)*0.5;
|
||||
if((Density(X,Y,Mid)<0.0f)==PreviousSolid)Lo=Mid;else Hi=Mid;
|
||||
}
|
||||
Node.Z=(Lo+Hi)*0.5;Node.bBelowSolid=PreviousSolid;Node.bValid=true;
|
||||
const float GX=Density(X+2,Y,Node.Z)-Density(X-2,Y,Node.Z);
|
||||
const float GY=Density(X,Y+2,Node.Z)-Density(X,Y-2,Node.Z);
|
||||
const float GZ=Density(X,Y,Node.Z+2)-Density(X,Y,Node.Z-2);
|
||||
Node.Normal=FVector(GX,GY,GZ);
|
||||
if(Node.Normal.SizeSquared()<1.0e-8)Node.Normal=FVector(0,0,PreviousSolid?1:-1);
|
||||
Node.Normal.Normalize();break;
|
||||
}
|
||||
}
|
||||
const auto EnsureVertex=[&](FNode& Node,int32 XI,int32 YI)
|
||||
{
|
||||
if(Node.Index!=INDEX_NONE)return Node.Index;
|
||||
Node.Index=static_cast<int32>(NewVertices.size());
|
||||
NewVertices.emplace_back((Origin.X+XI*FineStep)*25.0,(Origin.Y+YI*FineStep)*25.0,Node.Z*25.0);
|
||||
NewNormals.push_back(Node.Normal);
|
||||
if(bHasUVs)NewUVs.emplace_back(static_cast<float>(XI)*FineStep/256.0f,
|
||||
static_cast<float>(YI)*FineStep/256.0f);
|
||||
if(bHasColors)NewColors.push_back(Color);
|
||||
return Node.Index;
|
||||
};
|
||||
for(int32 YI=0;YI<GridCells;++YI)for(int32 XI=0;XI<GridCells;++XI)
|
||||
{
|
||||
FNode* Corners[4]={&Nodes[static_cast<size_t>(YI)*(GridCells+1)+XI],
|
||||
&Nodes[static_cast<size_t>(YI)*(GridCells+1)+XI+1],
|
||||
&Nodes[static_cast<size_t>(YI+1)*(GridCells+1)+XI+1],
|
||||
&Nodes[static_cast<size_t>(YI+1)*(GridCells+1)+XI]};
|
||||
int32 Valid[4],Count=0;
|
||||
for(int32 I=0;I<4;++I)if(Corners[I]->bValid)Valid[Count++]=I;
|
||||
if(Count<3)continue;
|
||||
int32 Indices[4];
|
||||
const int32 CornerXI[4]={XI,XI+1,XI+1,XI};
|
||||
const int32 CornerYI[4]={YI,YI,YI+1,YI+1};
|
||||
for(int32 I=0;I<Count;++I)
|
||||
{
|
||||
const int32 C=Valid[I];Indices[I]=EnsureVertex(*Corners[C],CornerXI[C],CornerYI[C]);
|
||||
}
|
||||
const bool bUp=Corners[Valid[0]]->bBelowSolid;
|
||||
if(Count==4)
|
||||
{
|
||||
if(bUp)NewTriangles.insert(NewTriangles.end(),{Indices[0],Indices[1],Indices[3],Indices[1],Indices[2],Indices[3]});
|
||||
else NewTriangles.insert(NewTriangles.end(),{Indices[0],Indices[3],Indices[1],Indices[1],Indices[3],Indices[2]});
|
||||
}
|
||||
else if(bUp)NewTriangles.insert(NewTriangles.end(),{Indices[0],Indices[1],Indices[2]});
|
||||
else NewTriangles.insert(NewTriangles.end(),{Indices[0],Indices[2],Indices[1]});
|
||||
}
|
||||
}
|
||||
if(NewVertices.empty()||NewTriangles.empty())return;
|
||||
const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,OldVertexCount);
|
||||
const int32 AddedVertices=static_cast<int32>(NewVertices.size());
|
||||
auto InsertVertices=[&](auto& Existing,const auto& Added)
|
||||
{
|
||||
using TArrayType=std::remove_reference_t<decltype(Existing)>;
|
||||
TArrayType Combined;Combined.Reserve(Existing.Num()+static_cast<int32>(Added.size()));
|
||||
for(int32 I=0;I<OldSurfaceVertices;++I)Combined.Add(Existing[I]);
|
||||
for(const auto& Value:Added)Combined.Add(Value);
|
||||
for(int32 I=OldSurfaceVertices;I<Existing.Num();++I)Combined.Add(Existing[I]);
|
||||
Existing=std::move(Combined);
|
||||
};
|
||||
InsertVertices(Mesh.Vertices,NewVertices);InsertVertices(Mesh.Normals,NewNormals);
|
||||
if(bHasUVs)InsertVertices(Mesh.UVs,NewUVs);
|
||||
if(bHasColors)InsertVertices(Mesh.Colors,NewColors);
|
||||
for(int32& Index:Mesh.Triangles)if(Index>=OldSurfaceVertices)Index+=AddedVertices;
|
||||
const int32 CeilingStart=std::max(0,Mesh.Triangles.Num()-Mesh.NumCeilingTriangles*3);
|
||||
TArray<int32> CombinedTriangles;CombinedTriangles.Reserve(Mesh.Triangles.Num()+static_cast<int32>(NewTriangles.size()));
|
||||
for(int32 I=0;I<CeilingStart;++I)CombinedTriangles.Add(Mesh.Triangles[I]);
|
||||
for(const int32 Index:NewTriangles)CombinedTriangles.Add(OldSurfaceVertices+Index);
|
||||
for(int32 I=CeilingStart;I<Mesh.Triangles.Num();++I)CombinedTriangles.Add(Mesh.Triangles[I]);
|
||||
Mesh.Triangles=std::move(CombinedTriangles);Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertices;
|
||||
}
|
||||
|
||||
struct FStandaloneMeshCacheMetadata
|
||||
{
|
||||
uint64 SourceDataHash=0;
|
||||
@@ -556,6 +828,12 @@ private:
|
||||
const int32 Extent=Job.Key.ExtentVoxels();
|
||||
const int32 Cells=Level<World->FullResClipLevels?CHUNK_SIZE:FMath::Max(1,World->CoarseTileCells);
|
||||
const int32 Step=FMath::Max(1,Extent/Cells);
|
||||
std::unordered_set<uint64> ForcedFarAirSamples;
|
||||
const FStandaloneShapeWorld* Shape=FStandaloneShapeWorldAccess::GetActive();
|
||||
if(Level>=6&&Shape&&Shape->HasFarWorld())
|
||||
Shape->BuildFarConnectorAirSamples(Job.Key.OriginVoxels(),Step,Cells,ForcedFarAirSamples);
|
||||
FStandaloneShapeWorld::FScopedForcedFarAirSamples ForcedSamplesScope(
|
||||
ForcedFarAirSamples.empty()?nullptr:&ForcedFarAirSamples);
|
||||
int64 SampleCount=0;
|
||||
const bool bProfile=VoxelDensityProfile::GetMode()!=VoxelDensityProfile::EMode::Disabled;
|
||||
const VoxelDensityProfile::FSnapshot Before=bProfile
|
||||
@@ -564,6 +842,11 @@ private:
|
||||
FVoxelMeshData Mesh=Mesher.GenerateMesh(Job.Key.OriginVoxels(),Step,Cells,nullptr,
|
||||
Job.BandZMin,Job.BandZMax,&SampleCount,nullptr,nullptr,
|
||||
bHashDensity?&DensityValues:nullptr);
|
||||
if(Shape&&Shape->HasFarWorld())
|
||||
{
|
||||
AppendFarConnectorShells(*Shape,Job.Key,Step,Mesh);
|
||||
AppendFarStratumShells(*World,*Shape,Job.Key,Step,Mesh);
|
||||
}
|
||||
if(bHashDensity)
|
||||
{
|
||||
Out.bHasDensityHash=true;
|
||||
|
||||
Reference in new issue
Block a user