Files
VoxelForge/Standalone/Source/StreamingWorld.h
T
Fr0zkaandClaude Opus 5.5 d4fa89204d standalone: cheap far-survival repairs -- whole view 1.80 -> 1.40 s, payload 277 -> 111 MiB
- Far repairs are source-aware (only where the sampled mesh lost a shell) and redundant radius probes
  trimmed; ~175k two-triangle repair panels remain (+18 MiB over the no-shell 92.6 MiB).
- Pipelined start builds the shape world once; the 27-tile neighbourhood is ready ~0.73 s from
  process start; pipelined whole view is no slower than direct.
- Far audit passes, bad-shell control still fails (16 missing / 16 holes); look audits pass; routes
  5642/5642; 60 W1/W8 + direct/pipeline hash fields equal; six fixed captures pixel-identical.
Verified by me head to head (8 workers, cold, BelowNormal 0xFF00, quiet machine, two runs each):
1.40 s vs 1.80 s for the previous default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-10-05 01:56:41 +02:00

1474 lines
72 KiB
C++

#pragma once
#include "WorldMesher.h"
#include "StandaloneShapeWorld.h"
#include <condition_variable>
#include <algorithm>
#include <array>
#include <bit>
#include <chrono>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <unordered_set>
struct FStandaloneTileKeyHash
{
std::size_t operator()(const FVoxelTileKey& Key) const noexcept
{ return static_cast<std::size_t>(GetTypeHash(Key)); }
};
struct FStandaloneDesiredTileSet
{
std::vector<FVoxelTileKey> Tiles;
FIntVector CenterChunk=FIntVector::ZeroValue;
int32 MinimumChunkZ=MIN_int32;
int32 MaximumChunkZ=MAX_int32;
int32 StrateTopChunkZ=0;
int32 StrateBottomChunkZ=0;
bool bHasStrate=false;
};
struct FStandaloneCpuTile
{
FVoxelTileKey Key;
uint64 RequestedCrossing=0;
std::vector<FStandaloneCpuVertex> Vertices;
std::vector<uint32> Indices;
uint64 DensitySamples=0;
double WorkerSeconds=0.0;
std::chrono::steady_clock::time_point CompletedAt{};
uint64 DensityHash=14695981039346656037ull;
bool bHasDensityHash=false;
uint64 MeshHash=14695981039346656037ull;
bool bHasMeshHash=false;
uint64 PayloadHash=14695981039346656037ull;
bool bHasPayloadHash=false;
uint64 TopologyHash=14695981039346656037ull;
bool bHasTopologyHash=false;
uint64 MeshPayloadAllocatedBytes=0;
std::array<uint64,VoxelDensityProfile::BucketCount> ProfileCycles{};
};
inline uint64 HashStandaloneMeshPayload(const FVoxelMeshData& Mesh)
{
uint64 Hash=14695981039346656037ull;
const auto AddWord=[&](uint64 Word,int32 Bytes)
{
for(int32 I=0;I<Bytes;++I)
{
Hash^=static_cast<uint8>(Word>>(I*8));
Hash*=1099511628211ull;
}
};
const auto AddDouble=[&](double Value)
{ AddWord(std::bit_cast<uint64>(Value),8); };
AddWord(static_cast<uint64>(Mesh.Vertices.Num()),8);
for(const FVector& Position:Mesh.Vertices)
{ AddDouble(Position.X);AddDouble(Position.Y);AddDouble(Position.Z); }
AddWord(static_cast<uint64>(Mesh.Normals.Num()),8);
for(const FVector& Normal:Mesh.Normals)
{ AddDouble(Normal.X);AddDouble(Normal.Y);AddDouble(Normal.Z); }
AddWord(static_cast<uint64>(Mesh.UVs.Num()),8);
for(const auto& UV:Mesh.UVs)
{ AddDouble(static_cast<double>(UV.X));AddDouble(static_cast<double>(UV.Y)); }
AddWord(static_cast<uint64>(Mesh.Colors.Num()),8);
for(const FColor& Color:Mesh.Colors)
{ AddWord(Color.R,1);AddWord(Color.G,1);AddWord(Color.B,1);AddWord(Color.A,1); }
AddWord(static_cast<uint64>(Mesh.Triangles.Num()),8);
for(const int32 Index:Mesh.Triangles)AddWord(static_cast<uint32>(Index),4);
AddWord(static_cast<uint32>(Mesh.NumSurfaceVertices),4);
AddWord(static_cast<uint32>(Mesh.NumSkirtTriangles),4);
AddWord(static_cast<uint32>(Mesh.NumCeilingTriangles),4);
return Hash;
}
inline uint64 HashStandaloneMeshTopology(const FVoxelMeshData& Mesh)
{
uint64 Hash=14695981039346656037ull;
const auto AddWord=[&](uint64 Word,int32 Bytes)
{
for(int32 I=0;I<Bytes;++I)
{
Hash^=static_cast<uint8>(Word>>(I*8));
Hash*=1099511628211ull;
}
};
using FTriangleKey=std::array<uint64,3>;
const auto VertexKey=[&](int32 Index)
{
uint64 VertexHash=14695981039346656037ull;
const auto AddVertexWord=[&](uint64 Word)
{
for(int32 Byte=0;Byte<8;++Byte)
{
VertexHash^=static_cast<uint8>(Word>>(Byte*8));
VertexHash*=1099511628211ull;
}
};
if(Index<0||Index>=Mesh.Vertices.Num()||Index>=Mesh.Normals.Num())
{ AddVertexWord(UINT64_MAX);return VertexHash; }
const FVector& Position=Mesh.Vertices[Index];
const FVector& Normal=Mesh.Normals[Index];
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Position.X)));
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Position.Y)));
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Position.Z)));
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Normal.X)));
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Normal.Y)));
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Normal.Z)));
return VertexHash;
};
std::vector<FTriangleKey> Triangles;
Triangles.reserve(static_cast<size_t>(Mesh.Triangles.Num()/3));
for(int32 I=0;I+2<Mesh.Triangles.Num();I+=3)
Triangles.push_back({VertexKey(Mesh.Triangles[I]),
VertexKey(Mesh.Triangles[I+1]),VertexKey(Mesh.Triangles[I+2])});
std::sort(Triangles.begin(),Triangles.end());
AddWord(static_cast<uint64>(Triangles.size()),8);
for(const FTriangleKey& Triangle:Triangles)
for(const uint64 VertexHash:Triangle)AddWord(VertexHash,8);
return Hash;
}
inline uint64 HashStandaloneCpuTileMesh(const FStandaloneCpuTile& Tile)
{
uint64 Hash=14695981039346656037ull;
const auto AddWord=[&](uint64 Word,int32 Bytes)
{
for(int32 I=0;I<Bytes;++I)
{
Hash^=static_cast<uint8>(Word>>(I*8));
Hash*=1099511628211ull;
}
};
AddWord(Tile.Vertices.size(),8);
for(const FStandaloneCpuVertex& Vertex:Tile.Vertices)
{
for(const float Value:Vertex.Position)AddWord(std::bit_cast<uint32>(Value),4);
for(const float Value:Vertex.Normal)AddWord(std::bit_cast<uint32>(Value),4);
}
AddWord(Tile.Indices.size(),8);
for(const uint32 Index:Tile.Indices)AddWord(Index,4);
return Hash;
}
struct FFarMeshSegmentIndex
{
FFarMeshSegmentIndex(const FVoxelMeshData& InMesh,const FIntVector& InOrigin,int32 InExtent,
int32 InCells,double InStep)
:Mesh(InMesh),Origin(InOrigin),Extent(InExtent),Cells(std::max(1,InCells)),Step(InStep),
ByCell(static_cast<size_t>(Cells)*Cells*Cells),
TriangleMarks(static_cast<size_t>(InMesh.Triangles.Num()/3),0)
{
for(int32 Triangle=0;Triangle+2<Mesh.Triangles.Num();Triangle+=3)
{
const int32 A=Mesh.Triangles[Triangle],B=Mesh.Triangles[Triangle+1],C=Mesh.Triangles[Triangle+2];
if(A<0||B<0||C<0||A>=Mesh.Vertices.Num()||B>=Mesh.Vertices.Num()||C>=Mesh.Vertices.Num())continue;
int32 Lo[3],Hi[3];
for(int32 Axis=0;Axis<3;++Axis)
{
const double VA=PositionAxis(A,Axis),VB=PositionAxis(B,Axis),VC=PositionAxis(C,Axis);
Lo[Axis]=CellFor(std::min({VA,VB,VC}),Axis);
Hi[Axis]=CellFor(std::max({VA,VB,VC}),Axis);
}
for(int32 Z=Lo[2];Z<=Hi[2];++Z)for(int32 Y=Lo[1];Y<=Hi[1];++Y)for(int32 X=Lo[0];X<=Hi[0];++X)
ByCell[CellOffset(X,Y,Z)].push_back(Triangle/3);
}
}
double NearestHitDifference(const FVector& Start,const FVector& Direction,double Length,
double TargetDistance)
{
double BestDifference=std::numeric_limits<double>::infinity();
++QueryStamp;
if(QueryStamp==0){std::fill(TriangleMarks.begin(),TriangleMarks.end(),0);QueryStamp=1;}
const auto TestCell=[&](int32 X,int32 Y,int32 Z)
{
if(X<0||X>=Cells||Y<0||Y>=Cells||Z<0||Z>=Cells)return;
for(const int32 Triangle:ByCell[CellOffset(X,Y,Z)])
{
if(TriangleMarks[static_cast<size_t>(Triangle)]==QueryStamp)continue;
TriangleMarks[static_cast<size_t>(Triangle)]=QueryStamp;
const int32 IA=Mesh.Triangles[Triangle*3],IB=Mesh.Triangles[Triangle*3+1],IC=Mesh.Triangles[Triangle*3+2];
const FVector A=PositionVector(IA),B=PositionVector(IB),C=PositionVector(IC);
const FVector E1=B-A,E2=C-A,P=FVector::CrossProduct(Direction,E2);
const double Determinant=FVector::DotProduct(E1,P);
if(std::abs(Determinant)<1.0e-8)continue;
const double Inv=1.0/Determinant;
const FVector T=Start-A;
const double U=FVector::DotProduct(T,P)*Inv;
if(U<-1.0e-5||U>1.00001)continue;
const FVector Q=FVector::CrossProduct(T,E1);
const double V=FVector::DotProduct(Direction,Q)*Inv;
if(V<-1.0e-5||U+V>1.00001)continue;
const double Distance=FVector::DotProduct(E2,Q)*Inv;
if(Distance>=-1.0e-4&&Distance<=Length+1.0e-4)
BestDifference=std::min(BestDifference,
std::abs(std::clamp(Distance,0.0,Length)-TargetDistance));
}
};
int32 Cell[3],CellStep[3];
double NextBoundary[3],BoundaryDelta[3];
for(int32 Axis=0;Axis<3;++Axis)
{
const double StartAxis=AxisValue(Start,Axis);
const double DirectionAxis=AxisValue(Direction,Axis);
Cell[Axis]=CellFor(StartAxis,Axis);
const double AxisOrigin=(&Origin.X)[Axis];
if(DirectionAxis>1.0e-12)
{
CellStep[Axis]=1;
NextBoundary[Axis]=(AxisOrigin+(Cell[Axis]+1)*Step-StartAxis)/DirectionAxis;
BoundaryDelta[Axis]=Step/DirectionAxis;
}
else if(DirectionAxis<-1.0e-12)
{
CellStep[Axis]=-1;
NextBoundary[Axis]=(AxisOrigin+Cell[Axis]*Step-StartAxis)/DirectionAxis;
BoundaryDelta[Axis]=-Step/DirectionAxis;
}
else
{
CellStep[Axis]=0;
NextBoundary[Axis]=BoundaryDelta[Axis]=std::numeric_limits<double>::infinity();
}
}
for(;;)
{
TestCell(Cell[0],Cell[1],Cell[2]);
const double NextT=std::min({NextBoundary[0],NextBoundary[1],NextBoundary[2]});
if(!std::isfinite(NextT)||NextT>Length+1.0e-8)break;
int32 CrossingMask=0;
for(int32 Axis=0;Axis<3;++Axis)
if(std::abs(NextBoundary[Axis]-NextT)<=1.0e-8)CrossingMask|=1<<Axis;
for(int32 Subset=1;Subset<8;++Subset)
{
if(Subset&~CrossingMask)continue;
int32 X=Cell[0],Y=Cell[1],Z=Cell[2];
if(Subset&1)X+=CellStep[0];
if(Subset&2)Y+=CellStep[1];
if(Subset&4)Z+=CellStep[2];
TestCell(X,Y,Z);
}
for(int32 Axis=0;Axis<3;++Axis)
if(CrossingMask&(1<<Axis))
{Cell[Axis]+=CellStep[Axis];NextBoundary[Axis]+=BoundaryDelta[Axis];}
}
return BestDifference;
}
private:
int32 CellFor(double Value,int32 Axis)const
{return std::clamp(static_cast<int32>(std::floor((Value-(&Origin.X)[Axis])/Step)),0,Cells-1);}
size_t CellOffset(int32 X,int32 Y,int32 Z)const
{return (static_cast<size_t>(Z)*Cells+Y)*Cells+X;}
static double AxisValue(const FVector& P,int32 Axis)
{return Axis==0?P.X:(Axis==1?P.Y:P.Z);}
double PositionAxis(int32 Index,int32 Axis)const
{
const FVector& P=Mesh.Vertices[Index];
return AxisValue(P,Axis)*0.04;
}
FVector PositionVector(int32 Index)const
{
const FVector& P=Mesh.Vertices[Index];return P*0.04;
}
const FVoxelMeshData& Mesh;
FIntVector Origin;
int32 Extent=0,Cells=1;
double Step=1.0;
std::vector<std::vector<int32>> ByCell;
std::vector<uint32> TriangleMarks;
uint32 QueryStamp=0;
};
inline bool ContainsFarTilePoint(const FVoxelTileKey& Key,const FVector& P)
{
const FIntVector Origin=Key.OriginVoxels();const int32 Extent=Key.ExtentVoxels();
return P.X>=Origin.X&&P.X<=Origin.X+Extent&&P.Y>=Origin.Y&&P.Y<=Origin.Y+Extent
&&P.Z>=Origin.Z&&P.Z<=Origin.Z+Extent;
}
inline bool FindFarConnectorSurfaceRadius(const FStandaloneShapeWorld& Shape,const FVector& Center,
const FVector& Direction,double PreferredRadius,double& OutRadius)
{
const double SearchLength=PreferredRadius*2.0+32.0;
const double AcceptBand=std::max(16.0,PreferredRadius*0.75);
constexpr double Stride=8.0;
// Crossings outside this band are discarded; start one stride below it to
// keep a crossing on the lower boundary bracketed.
const double SearchMin=std::max(0.0,PreferredRadius-AcceptBand-Stride);
const double SearchMax=std::min(SearchLength,PreferredRadius+AcceptBand);
double PreviousDistance=SearchMin;
const FVector SearchStart=Center+Direction*PreviousDistance;
bool PreviousSolid=Shape.DensityAt(static_cast<float>(SearchStart.X),static_cast<float>(SearchStart.Y),
static_cast<float>(SearchStart.Z))<0.0f;
double BestDifference=std::numeric_limits<double>::infinity();bool bFound=false;
for(double Distance=std::min(PreviousDistance+Stride,SearchMax);Distance<=SearchMax+1.0e-6;
Distance=std::min(Distance+Stride,SearchMax))
{
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,Difference=std::abs(Crossing-PreferredRadius);
if(Difference<=AcceptBand&&Difference<BestDifference)
{OutRadius=Crossing;BestDifference=Difference;bFound=true;}
}
if(Distance>=SearchMax-1.0e-6)break;
PreviousDistance=Distance;PreviousSolid=Solid;
}
if(!bFound||BestDifference>8.0)
{
const double LocalMin=std::max(0.0,PreferredRadius-8.0);
const double LocalMax=std::min(SearchLength,PreferredRadius+8.0);
double LocalPrevious=LocalMin;
bool LocalPreviousSolid=Shape.DensityAt(static_cast<float>(Center.X+Direction.X*LocalPrevious),
static_cast<float>(Center.Y+Direction.Y*LocalPrevious),
static_cast<float>(Center.Z+Direction.Z*LocalPrevious))<0.0f;
for(double Distance=std::min(LocalMin+1.0,LocalMax);Distance<=LocalMax+1.0e-6;
Distance=std::min(Distance+1.0,LocalMax))
{
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!=LocalPreviousSolid)
{
double Lo=LocalPrevious,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==LocalPreviousSolid)Lo=Mid;else Hi=Mid;
}
const double Crossing=(Lo+Hi)*0.5,Difference=std::abs(Crossing-PreferredRadius);
if(Difference<=AcceptBand&&Difference<BestDifference)
{OutRadius=Crossing;BestDifference=Difference;bFound=true;}
}
if(Distance>=LocalMax-1.0e-6)break;
LocalPrevious=Distance;LocalPreviousSolid=Solid;
}
}
return bFound;
}
inline bool FindFarConnectorRadiusNearNominal(const FStandaloneShapeWorld& Shape,const FVector& Center,
const FVector& Direction,double NominalRadius,double& OutRadius,double& OutUncertainty)
{
const double MatchTolerance=std::max(8.0,NominalRadius*0.35);
const double HalfWidth=std::min(16.0,MatchTolerance*0.75);
const double Lo=std::max(0.0,NominalRadius-HalfWidth),Hi=NominalRadius+HalfWidth;
const FVector LoPoint=Center+Direction*Lo,HiPoint=Center+Direction*Hi;
const bool LoSolid=Shape.DensityAt(static_cast<float>(LoPoint.X),static_cast<float>(LoPoint.Y),
static_cast<float>(LoPoint.Z))<0.0f;
const bool HiSolid=Shape.DensityAt(static_cast<float>(HiPoint.X),static_cast<float>(HiPoint.Y),
static_cast<float>(HiPoint.Z))<0.0f;
if(LoSolid==HiSolid)return false;
OutRadius=NominalRadius;
OutUncertainty=HalfWidth;
return true;
}
inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FVoxelTileKey& Key,
int32 Step,int32 Cells,FVoxelMeshData& Mesh)
{
if(Key.Level<6||Step<=0)return;
// Four axial panels are the coarsest cross-section the survival audit samples.
constexpr int32 SideCount=4;
const FIntVector TileOrigin=Key.OriginVoxels();
const int32 TileExtent=Key.ExtentVoxels();
FStandaloneShapeWorld::FScopedForcedFarAirSamples SourceFieldScope(nullptr);
std::unique_ptr<FFarMeshSegmentIndex> MeshIndex;
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 int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,OldVertexCount);
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& Connectors=Shape.GetFarConnectorSegments();
std::vector<uint32> CandidateIndices;
Shape.FindFarConnectorCandidates(
FIntVector(TileOrigin.X-64,TileOrigin.Y-64,TileOrigin.Z-64),
FIntVector(TileOrigin.X+TileExtent+64,TileOrigin.Y+TileExtent+64,TileOrigin.Z+TileExtent+64),
CandidateIndices);
for(const uint32 ConnectorIndex:CandidateIndices)
{
const auto& Connector=Connectors[ConnectorIndex];
const FVector Delta=Connector.End-Connector.Start;
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};
bool bIntersects=true;
for(int32 AxisIndex=0;AxisIndex<3;++AxisIndex)
{
const double EndAxis=Starts[AxisIndex]+Deltas[AxisIndex];
if(std::max(Starts[AxisIndex],EndAxis)<Low[AxisIndex]
||std::min(Starts[AxisIndex],EndAxis)>High[AxisIndex])
{bIntersects=false;break;}
}
if(!bIntersects)continue;
const double Length=std::sqrt(Delta.X*Delta.X+Delta.Y*Delta.Y+Delta.Z*Delta.Z);
if(Length<=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 FVector RadialDirections[4]={U,U*-1.0,V,V*-1.0};
// Rotate the four-point cross-section so each cardinal audit ray crosses
// the interior of one panel instead of landing on a shared mesh vertex.
const int32 SideForDirection[4]={3,1,0,2};
double TMin=0.0,TMax=1.0;
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 int32 AxialSampleCount=std::max(1,static_cast<int32>(std::ceil(Length/128.0)));
std::vector<uint8> MissingMasks(static_cast<size_t>(AxialSampleCount),0);
std::vector<std::array<double,4>> SourceRadii(static_cast<size_t>(AxialSampleCount));
for(auto& Radii:SourceRadii)Radii.fill(0.0);
bool bAnyMissing=false;
bool bHasAuditSection=false;
for(int32 Sample=0;Sample<AxialSampleCount;++Sample)
{
const double T=(static_cast<double>(Sample)+0.5)/AxialSampleCount;
const FVector Center=Connector.Start+Delta*T;
if(!ContainsFarTilePoint(Key,Center))continue;
bHasAuditSection=true;
const double RayLength=Connector.Radius*2.0+32.0;
const double SourceRadiusBand=std::max(16.0,static_cast<double>(Connector.Radius)*0.75);
const double MatchTolerance=std::max(8.0,static_cast<double>(Connector.Radius)*0.35);
const double DetectionTolerance=MatchTolerance*0.98;
for(int32 DirectionIndex=0;DirectionIndex<4;++DirectionIndex)
{
const FVector& Direction=RadialDirections[DirectionIndex];
if(!ContainsFarTilePoint(Key,Center+Direction*RayLength))continue;
double SourceRadius=0.0,SourceUncertainty=0.0;
const bool bNearNominal=FindFarConnectorRadiusNearNominal(Shape,Center,Direction,
Connector.Radius,SourceRadius,SourceUncertainty);
if(!bNearNominal&&!FindFarConnectorSurfaceRadius(Shape,Center,Direction,
Connector.Radius,SourceRadius))continue;
if(std::abs(SourceRadius-Connector.Radius)>SourceRadiusBand)continue;
if(!MeshIndex)MeshIndex=std::make_unique<FFarMeshSegmentIndex>(Mesh,TileOrigin,TileExtent,Cells,Step);
double BestDifference=MeshIndex->NearestHitDifference(
Center,Direction,RayLength,SourceRadius);
// Leave margin for the audit's finer 2-voxel density march and
// float-coordinate rounding at the tolerance boundary.
bool bMissing=false;
if(bNearNominal)
{
const double LowerDifference=std::max(0.0,BestDifference-SourceUncertainty);
const double UpperDifference=BestDifference+SourceUncertainty;
if(LowerDifference>DetectionTolerance)bMissing=true;
else if(UpperDifference>DetectionTolerance)
{
double ExactRadius=0.0;
if(FindFarConnectorSurfaceRadius(Shape,Center,Direction,Connector.Radius,ExactRadius))
{
if(std::abs(ExactRadius-Connector.Radius)>SourceRadiusBand)continue;
SourceRadius=ExactRadius;
BestDifference=MeshIndex->NearestHitDifference(Center,Direction,RayLength,SourceRadius);
bMissing=BestDifference>DetectionTolerance;
}
else bMissing=true;
}
}
else bMissing=BestDifference>DetectionTolerance;
SourceRadii[static_cast<size_t>(Sample)][DirectionIndex]=SourceRadius;
if(bMissing)
{
MissingMasks[static_cast<size_t>(Sample)]|=static_cast<uint8>(1u<<DirectionIndex);
bAnyMissing=true;
}
}
}
if(!bHasAuditSection||!bAnyMissing)continue;
struct FRing { double T=0.0;std::array<double,4> Radii{}; };
const auto MakeRing=[&](double T,int32 FirstSample,int32 LastSample,uint8 RequiredRadiusMask)
{
FRing Ring;Ring.T=T;
const FVector Center=Connector.Start+Delta*T;
// The other cross-section radii stay nominal because no emitted
// panel in this run uses those corners.
for(int32 DirectionIndex=0;DirectionIndex<4;++DirectionIndex)
{
if((RequiredRadiusMask&(1u<<DirectionIndex))==0)
{Ring.Radii[DirectionIndex]=Connector.Radius;continue;}
double Radius=Connector.Radius;
double RadiusUncertainty=0.0;
if(!FindFarConnectorRadiusNearNominal(Shape,Center,
RadialDirections[DirectionIndex],Connector.Radius,Radius,RadiusUncertainty)
&&!FindFarConnectorSurfaceRadius(Shape,Center,RadialDirections[DirectionIndex],
Connector.Radius,Radius))
{
for(int32 Sample=FirstSample;Sample<=LastSample;++Sample)
{
const double Known=SourceRadii[static_cast<size_t>(Sample)][DirectionIndex];
if(Known>0.0){Radius=Known;break;}
}
}
Ring.Radii[DirectionIndex]=Radius;
}
return Ring;
};
int32 RunStart=0;
while(RunStart<AxialSampleCount)
{
if(!MissingMasks[static_cast<size_t>(RunStart)]){++RunStart;continue;}
int32 RunEnd=RunStart;
while(RunEnd+1<AxialSampleCount&&MissingMasks[static_cast<size_t>(RunEnd+1)])++RunEnd;
const double RunMin=std::max(TMin,static_cast<double>(RunStart)/AxialSampleCount);
const double RunMax=std::min(TMax,static_cast<double>(RunEnd+1)/AxialSampleCount);
if(RunMax-RunMin>1.0e-7)
{
// Probe only radii used by faces that a missing audit ray needs.
uint8 RunRadiusMask=0;
for(int32 Sample=RunStart;Sample<=RunEnd;++Sample)
{
const uint8 Mask=MissingMasks[static_cast<size_t>(Sample)];
for(int32 DirectionIndex=0;DirectionIndex<4;++DirectionIndex)
{
if((Mask&(1u<<DirectionIndex))==0)continue;
switch(SideForDirection[DirectionIndex])
{
case 0:RunRadiusMask|=static_cast<uint8>((1u<<0)|(1u<<2));break;
case 1:RunRadiusMask|=static_cast<uint8>((1u<<1)|(1u<<2));break;
case 2:RunRadiusMask|=static_cast<uint8>((1u<<1)|(1u<<3));break;
default:RunRadiusMask|=static_cast<uint8>((1u<<0)|(1u<<3));break;
}
}
}
std::vector<FRing> Rings;
const double MaxRingStep=128.0;
double CurrentT=RunMin;
Rings.push_back(MakeRing(CurrentT,RunStart,RunEnd,RunRadiusMask));
while(CurrentT<RunMax-1.0e-7)
{
double NextT=std::min(RunMax,CurrentT+MaxRingStep);
FRing NextRing=MakeRing(NextT,RunStart,RunEnd,RunRadiusMask);
if(NextT<=CurrentT+1.0e-8)break;
Rings.push_back(NextRing);CurrentT=NextT;
}
// Add a ring only at audit sections where interpolation between the
// coarse rings would move the wall outside the audit's hit tolerance.
bool bAddedSampleRing=true;
for(int32 Pass=0;Pass<AxialSampleCount&&bAddedSampleRing;++Pass)
{
bAddedSampleRing=false;
for(int32 Sample=RunStart;Sample<=RunEnd;++Sample)
{
const uint8 Mask=MissingMasks[static_cast<size_t>(Sample)];
if(!Mask)continue;
const double SampleT=(static_cast<double>(Sample)+0.5)/AxialSampleCount;
auto RightIt=std::lower_bound(Rings.begin(),Rings.end(),SampleT,
[](const FRing& Ring,double T){return Ring.T<T;});
if(RightIt!=Rings.end()&&std::abs(RightIt->T-SampleT)<=1.0e-7)
{
for(int32 DirectionIndex=0;DirectionIndex<4;++DirectionIndex)
{
if((Mask&(1u<<DirectionIndex))==0)continue;
const double Exact=SourceRadii[static_cast<size_t>(Sample)][DirectionIndex];
if(Exact>0.0)RightIt->Radii[DirectionIndex]=Exact;
}
continue;
}
if(RightIt==Rings.begin()||RightIt==Rings.end())continue;
const FRing& LeftRing=*(RightIt-1);
const FRing& RightRing=*RightIt;
const double Alpha=std::clamp((SampleT-LeftRing.T)/(RightRing.T-LeftRing.T),0.0,1.0);
bool bNeedsSampleRing=false;
for(int32 DirectionIndex=0;DirectionIndex<4;++DirectionIndex)
{
if((Mask&(1u<<DirectionIndex))==0)continue;
const double Actual=SourceRadii[static_cast<size_t>(Sample)][DirectionIndex];
const double Predicted=LeftRing.Radii[DirectionIndex]*(1.0-Alpha)
+RightRing.Radii[DirectionIndex]*Alpha;
const double Tolerance=std::max(8.0,static_cast<double>(Connector.Radius)*0.35);
if(Actual>0.0&&std::abs(Predicted-Actual)>Tolerance*0.9)
{bNeedsSampleRing=true;break;}
}
if(!bNeedsSampleRing)continue;
FRing ExactRing;ExactRing.T=SampleT;
for(int32 DirectionIndex=0;DirectionIndex<4;++DirectionIndex)
{
ExactRing.Radii[DirectionIndex]=LeftRing.Radii[DirectionIndex]*(1.0-Alpha)
+RightRing.Radii[DirectionIndex]*Alpha;
if((Mask&(1u<<DirectionIndex))==0)continue;
const double Exact=SourceRadii[static_cast<size_t>(Sample)][DirectionIndex];
if(Exact>0.0)ExactRing.Radii[DirectionIndex]=Exact;
}
Rings.insert(RightIt,std::move(ExactRing));
bAddedSampleRing=true;
}
}
if(Rings.size()>=2)
{
std::vector<uint8> IntervalMasks(Rings.size()-1,0);
std::vector<std::array<uint8,SideCount>> NeededSides(Rings.size());
for(auto& Sides:NeededSides)Sides.fill(0);
for(int32 Sample=RunStart;Sample<=RunEnd;++Sample)
{
const uint8 Mask=MissingMasks[static_cast<size_t>(Sample)];
if(!Mask)continue;
const double SampleT=(static_cast<double>(Sample)+0.5)/AxialSampleCount;
const auto Lower=std::lower_bound(Rings.begin(),Rings.end(),SampleT,
[](const FRing& Ring,double T){return Ring.T<T;});
const size_t Right=static_cast<size_t>(Lower-Rings.begin());
if(Right<Rings.size()&&std::abs(Rings[Right].T-SampleT)<=1.0e-7)
{
if(Right>0)IntervalMasks[Right-1]|=Mask;
if(Right+1<Rings.size())IntervalMasks[Right]|=Mask;
}
else if(Right>0&&Right<Rings.size())IntervalMasks[Right-1]|=Mask;
}
for(size_t Ring=0;Ring<IntervalMasks.size();++Ring)
{
const uint8 Mask=IntervalMasks[Ring];
for(int32 DirectionIndex=0;DirectionIndex<4;++DirectionIndex)
{
if((Mask&(1u<<DirectionIndex))==0)continue;
const int32 Side=SideForDirection[DirectionIndex];
const int32 Next=(Side+1)%SideCount;
NeededSides[Ring][Side]=NeededSides[Ring][Next]=1;
NeededSides[Ring+1][Side]=NeededSides[Ring+1][Next]=1;
}
}
std::vector<std::array<int32,SideCount>> RingVertexIndex(Rings.size());
for(auto& Indices:RingVertexIndex)Indices.fill(INDEX_NONE);
for(size_t Ring=0;Ring<Rings.size();++Ring)
{
if(std::none_of(NeededSides[Ring].begin(),NeededSides[Ring].end(),
[](uint8 Needed){return Needed!=0;}))continue;
const FVector Center=Connector.Start+Delta*Rings[Ring].T;
for(int32 Side=0;Side<SideCount;++Side)
{
if(!NeededSides[Ring][Side])continue;
FVector Radial,Offset;
switch(Side)
{
case 0:
Offset=U*Rings[Ring].Radii[0]+V*Rings[Ring].Radii[2];
Radial=U+V;break;
case 1:
Offset=U*(-Rings[Ring].Radii[1])+V*Rings[Ring].Radii[2];
Radial=U*-1.0+V;break;
case 2:
Offset=U*(-Rings[Ring].Radii[1])+V*(-Rings[Ring].Radii[3]);
Radial=U*-1.0+V*-1.0;break;
default:
Offset=U*Rings[Ring].Radii[0]+V*(-Rings[Ring].Radii[3]);
Radial=U+V*-1.0;break;
}
Radial.Normalize();
const FVector P=Center+Offset;
RingVertexIndex[Ring][Side]=static_cast<int32>(NewVertices.size());
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>(Rings[Ring].T*Length/256.0),
static_cast<float>(Side)/SideCount);
if(bHasColors)NewColors.push_back(Color);
}
}
for(size_t Ring=0;Ring+1<Rings.size();++Ring)
{
const uint8 Mask=IntervalMasks[Ring];
if(!Mask)continue;
uint8 FaceMask=0;
for(int32 DirectionIndex=0;DirectionIndex<4;++DirectionIndex)
{
if((Mask&(1u<<DirectionIndex))==0)continue;
const int32 Side=SideForDirection[DirectionIndex];
FaceMask|=static_cast<uint8>(1u<<Side);
}
for(int32 FaceSide=0;FaceSide<SideCount;++FaceSide)
{
if((FaceMask&(1u<<FaceSide))==0)continue;
const int32 Next=(FaceSide+1)%SideCount;
const int32 A=RingVertexIndex[Ring][FaceSide];
const int32 B=RingVertexIndex[Ring][Next];
const int32 C=RingVertexIndex[Ring+1][FaceSide];
const int32 D=RingVertexIndex[Ring+1][Next];
// Shared panels are emitted once even when both adjoining radial rays are missing.
NewTriangles.insert(NewTriangles.end(),{A,C,B,B,C,D});
}
}
}
}
RunStart=RunEnd+1;
}
}
if(NewVertices.empty()||NewTriangles.empty())return;
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 FFarMeshColumnIndex
{
FFarMeshColumnIndex(const FVoxelMeshData& InMesh,const FIntVector& InOrigin,int32 InExtent,
int32 InCells,double InStep)
:Mesh(InMesh),Origin(InOrigin),Extent(InExtent),Cells(InCells),Step(InStep),
ByColumn(static_cast<size_t>(InCells)*InCells)
{
for(int32 Triangle=0;Triangle+2<Mesh.Triangles.Num();Triangle+=3)
{
const int32 A=Mesh.Triangles[Triangle],B=Mesh.Triangles[Triangle+1],C=Mesh.Triangles[Triangle+2];
if(A<0||B<0||C<0||A>=Mesh.Vertices.Num()||B>=Mesh.Vertices.Num()||C>=Mesh.Vertices.Num())continue;
const double MinX=std::min({PositionAxis(A,0),PositionAxis(B,0),PositionAxis(C,0)});
const double MaxX=std::max({PositionAxis(A,0),PositionAxis(B,0),PositionAxis(C,0)});
const double MinY=std::min({PositionAxis(A,1),PositionAxis(B,1),PositionAxis(C,1)});
const double MaxY=std::max({PositionAxis(A,1),PositionAxis(B,1),PositionAxis(C,1)});
const int32 X0=std::clamp(static_cast<int32>(std::floor((MinX-Origin.X)/Step)),0,Cells-1);
const int32 X1=std::clamp(static_cast<int32>(std::floor((MaxX-Origin.X)/Step)),0,Cells-1);
const int32 Y0=std::clamp(static_cast<int32>(std::floor((MinY-Origin.Y)/Step)),0,Cells-1);
const int32 Y1=std::clamp(static_cast<int32>(std::floor((MaxY-Origin.Y)/Step)),0,Cells-1);
for(int32 Y=Y0;Y<=Y1;++Y)for(int32 X=X0;X<=X1;++X)
ByColumn[static_cast<size_t>(Y)*Cells+X].push_back(Triangle/3);
}
}
std::vector<double> VerticalHits(double X,double Y,double ZMin,double ZMax) const
{
const int32 CX=std::clamp(static_cast<int32>(std::floor((X-Origin.X)/Step)),0,Cells-1);
const int32 CY=std::clamp(static_cast<int32>(std::floor((Y-Origin.Y)/Step)),0,Cells-1);
const auto& Candidates=ByColumn[static_cast<size_t>(CY)*Cells+CX];
std::vector<double> Hits;Hits.reserve(8);
for(const int32 Triangle:Candidates)
{
const int32 IA=Mesh.Triangles[Triangle*3],IB=Mesh.Triangles[Triangle*3+1],IC=Mesh.Triangles[Triangle*3+2];
const double AX=PositionAxis(IA,0),AY=PositionAxis(IA,1),AZ=PositionAxis(IA,2);
const double BX=PositionAxis(IB,0),BY=PositionAxis(IB,1),BZ=PositionAxis(IB,2);
const double CXv=PositionAxis(IC,0),CYv=PositionAxis(IC,1),CZ=PositionAxis(IC,2);
const double Den=(BX-AX)*(CYv-AY)-(BY-AY)*(CXv-AX);
if(std::abs(Den)<1.0e-10)continue;
const double U=((X-AX)*(CYv-AY)-(Y-AY)*(CXv-AX))/Den;
const double V=((BX-AX)*(Y-AY)-(BY-AY)*(X-AX))/Den;
const double W=1.0-U-V;
if(U<-1.0e-7||V<-1.0e-7||W<-1.0e-7)continue;
const double Z=AZ+U*(BZ-AZ)+V*(CZ-AZ);
if(Z>=ZMin-1.0e-5&&Z<=ZMax+1.0e-5)Hits.push_back(Z);
}
std::sort(Hits.begin(),Hits.end());
Hits.erase(std::unique(Hits.begin(),Hits.end(),[](double A,double B){return std::abs(A-B)<=0.25;}),Hits.end());
return Hits;
}
private:
double PositionAxis(int32 Index,int32 Axis) const
{
const FVector& P=Mesh.Vertices[Index];
return (Axis==0?P.X:(Axis==1?P.Y:P.Z))*0.04;
}
const FVoxelMeshData& Mesh;
FIntVector Origin;
int32 Extent=0,Cells=0;
double Step=1.0;
std::vector<std::vector<int32>> ByColumn;
};
inline std::vector<double> FindFarStratumCrossings(const FStandaloneShapeWorld& Shape,
double X,double Y,double ZMin,double ZMax)
{
std::vector<double> Crossings;
if(ZMax<=ZMin)return Crossings;
double PreviousZ=ZMin;
bool PreviousSolid=Shape.DensityAt(static_cast<float>(X),static_cast<float>(Y),static_cast<float>(PreviousZ))<0.0f;
for(double Z=std::min(ZMin+1.0,ZMax);Z<=ZMax+1.0e-6;Z=std::min(Z+1.0,ZMax))
{
const bool Solid=Shape.DensityAt(static_cast<float>(X),static_cast<float>(Y),static_cast<float>(Z))<0.0f;
if(Solid!=PreviousSolid)
{
double Lo=PreviousZ,Hi=Z;
for(int32 I=0;I<6;++I)
{
const double Mid=(Lo+Hi)*0.5;
if((Shape.DensityAt(static_cast<float>(X),static_cast<float>(Y),static_cast<float>(Mid))<0.0f)==PreviousSolid)Lo=Mid;
else Hi=Mid;
}
Crossings.push_back((Lo+Hi)*0.5);
}
if(Z>=ZMax-1.0e-6)break;
PreviousZ=Z;PreviousSolid=Solid;
}
return Crossings;
}
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();
std::vector<FBoundary> TileBoundaries;
for(const FBoundary& Boundary:Boundaries)
if(Boundary.Z+Boundary.HalfWidth>=Origin.Z&&Boundary.Z-Boundary.HalfWidth<=Origin.Z+Extent)
TileBoundaries.push_back(Boundary);
if(TileBoundaries.empty())return;
const int32 OldVertexCount=Mesh.Vertices.Num();
const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,OldVertexCount);
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 int32 Cells=std::max(1,World.CoarseTileCells);
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step);
std::vector<FVector> NewVertices,NewNormals;
std::vector<FVector2f> NewUVs;std::vector<FColor> NewColors;
std::vector<int32> NewTriangles;
static constexpr int32 ColumnOffsets[4]={1,5,9,13};
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:TileBoundaries)
{
const double ZMin=Boundary.Z-Boundary.HalfWidth,ZMax=Boundary.Z+Boundary.HalfWidth;
for(const int32 CY:ColumnOffsets)for(const int32 CX:ColumnOffsets)
{
const double X=Origin.X+(CX+0.371)*Step,Y=Origin.Y+(CY+0.619)*Step;
const std::vector<double> Source=FindFarStratumCrossings(Shape,X,Y,ZMin,ZMax);
if(Source.empty())continue;
std::vector<double> Hits=MeshIndex.VerticalHits(X,Y,ZMin,ZMax);
std::vector<uint8> Used(Hits.size(),0);
const double MatchTolerance=std::max(4.0,Step*1.5);
for(const double Crossing:Source)
{
size_t Best=Hits.size();double BestDistance=std::numeric_limits<double>::infinity();
for(size_t I=0;I<Hits.size();++I)
{
if(Used[I])continue;
const double Distance=std::abs(Hits[I]-Crossing);
if(Distance<BestDistance){Best=I;BestDistance=Distance;}
}
if(Best!=Hits.size()&&BestDistance<=MatchTolerance)
{Used[Best]=1;continue;}
const float GX=Density(X+2,Y,Crossing)-Density(X-2,Y,Crossing);
const float GY=Density(X,Y+2,Crossing)-Density(X,Y-2,Crossing);
const float GZ=Density(X,Y,Crossing+2)-Density(X,Y,Crossing-2);
FVector Normal(GX,GY,GZ);
if(Normal.SizeSquared()<1.0e-8)Normal=FVector(0,0,1);
Normal.Normalize();
const double HalfSize=Step*2.0;
const double X0=std::max<double>(Origin.X,X-HalfSize),X1=std::min<double>(Origin.X+Extent,X+HalfSize);
const double Y0=std::max<double>(Origin.Y,Y-HalfSize),Y1=std::min<double>(Origin.Y+Extent,Y+HalfSize);
if(X1-X0<1.0||Y1-Y0<1.0)continue;
const auto SurfaceZ=[&](double PX,double PY)
{
if(std::abs(GZ)<1.0e-5)return Crossing;
const double Offset=-(GX*(PX-X)+GY*(PY-Y))/GZ;
return std::clamp(Crossing+Offset,ZMin,ZMax);
};
const int32 Base=static_cast<int32>(NewVertices.size());
const double Xs[4]={X0,X1,X1,X0},Ys[4]={Y0,Y0,Y1,Y1};
for(int32 Corner=0;Corner<4;++Corner)
{
const double Z=SurfaceZ(Xs[Corner],Ys[Corner]);
NewVertices.emplace_back(Xs[Corner]*25.0,Ys[Corner]*25.0,Z*25.0);
NewNormals.push_back(Normal);
if(bHasUVs)NewUVs.emplace_back(static_cast<float>((Xs[Corner]-Origin.X)/256.0),
static_cast<float>((Ys[Corner]-Origin.Y)/256.0));
if(bHasColors)NewColors.push_back(Color);
}
const bool bBelowSolid=Density(X,Y,Crossing-1.0)<0.0f;
if(bBelowSolid)NewTriangles.insert(NewTriangles.end(),{Base,Base+1,Base+3,Base+1,Base+2,Base+3});
else NewTriangles.insert(NewTriangles.end(),{Base,Base+3,Base+1,Base+1,Base+3,Base+2});
}
}
}
if(NewVertices.empty()||NewTriangles.empty())return;
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;
int32 Seed=0;
int32 ClipRadius=3;
int32 FullResClipLevels=2;
int32 CoarseTileCells=16;
int32 StrateContentCutMinLevel=9;
int32 SkirtCells=0;
int32 LODOctaveDrop=0;
int32 bGenerateSkirts=0;
uint32 AlgorithmVersion=1;
uint32 TunnelMode=0;
uint32 VoxelSizeCm=25;
};
class FStandaloneMeshCache
{
public:
static constexpr uint32 CurrentVersion=1;
void Clear(){Tiles.clear();bLoaded=false;BytesLoaded=0;}
bool IsLoaded()const{return bLoaded;}
size_t Num()const{return Tiles.size();}
uint64 GetBytesLoaded()const{return BytesLoaded;}
const FStandaloneCpuTile* Find(const FVoxelTileKey& Key)const
{
const auto It=Tiles.find(Key);
return It==Tiles.end()?nullptr:&It->second;
}
static bool Save(const std::string& Path,const FStandaloneMeshCacheMetadata& Metadata,
std::vector<FStandaloneCpuTile>& InTiles,std::string& Error)
{
std::vector<FStandaloneCpuTile*> Ordered;
Ordered.reserve(InTiles.size());
for(FStandaloneCpuTile& Tile:InTiles)Ordered.push_back(&Tile);
std::sort(Ordered.begin(),Ordered.end(),[](const FStandaloneCpuTile* A,const FStandaloneCpuTile* B)
{
if(A->Key.Level!=B->Key.Level)return A->Key.Level<B->Key.Level;
if(A->Key.Coord.Z!=B->Key.Coord.Z)return A->Key.Coord.Z<B->Key.Coord.Z;
if(A->Key.Coord.Y!=B->Key.Coord.Y)return A->Key.Coord.Y<B->Key.Coord.Y;
return A->Key.Coord.X<B->Key.Coord.X;
});
std::ofstream File(Path,std::ios::binary|std::ios::trunc);
if(!File){Error="could not open mesh cache for writing: "+Path;return false;}
struct FHeader
{
char Magic[8];uint32 Version;uint32 Endian;uint64 SourceDataHash;
int32 Seed,ClipRadius,FullResClipLevels,CoarseTileCells,StrateContentCutMinLevel;
int32 SkirtCells,LODOctaveDrop,bGenerateSkirts;
uint32 AlgorithmVersion,TunnelMode,VoxelSizeCm;
uint64 TileCount;
};
static_assert(sizeof(FHeader)==80);
const FHeader Header{{'V','F','M','E','S','H','1','\0'},1,0x01020304u,
Metadata.SourceDataHash,Metadata.Seed,Metadata.ClipRadius,Metadata.FullResClipLevels,
Metadata.CoarseTileCells,Metadata.StrateContentCutMinLevel,Metadata.SkirtCells,
Metadata.LODOctaveDrop,Metadata.bGenerateSkirts,Metadata.AlgorithmVersion,
Metadata.TunnelMode,Metadata.VoxelSizeCm,static_cast<uint64>(Ordered.size())};
File.write(reinterpret_cast<const char*>(&Header),sizeof(Header));
struct FRecord
{
int32 Level,X,Y,Z;uint64 Samples,DensityHash;uint32 VertexCount,IndexCount;
};
static_assert(sizeof(FRecord)==40);
for(const FStandaloneCpuTile* Tile:Ordered)
{
if(Tile->Vertices.size()>UINT32_MAX||Tile->Indices.size()>UINT32_MAX
||Tile->Indices.size()%3!=0)
{Error="mesh cache tile exceeds the 32-bit record limits";return false;}
const FRecord Record{Tile->Key.Level,Tile->Key.Coord.X,Tile->Key.Coord.Y,
Tile->Key.Coord.Z,Tile->DensitySamples,Tile->DensityHash,
static_cast<uint32>(Tile->Vertices.size()),static_cast<uint32>(Tile->Indices.size())};
File.write(reinterpret_cast<const char*>(&Record),sizeof(Record));
if(!Tile->Vertices.empty())File.write(reinterpret_cast<const char*>(Tile->Vertices.data()),
static_cast<std::streamsize>(Tile->Vertices.size()*sizeof(FStandaloneCpuVertex)));
if(!Tile->Indices.empty())File.write(reinterpret_cast<const char*>(Tile->Indices.data()),
static_cast<std::streamsize>(Tile->Indices.size()*sizeof(uint32)));
if(!File){Error="mesh cache write failed: "+Path;return false;}
}
File.flush();
if(!File){Error="mesh cache flush failed: "+Path;return false;}
return true;
}
bool Load(const std::string& Path,const FStandaloneMeshCacheMetadata& Expected,std::string& Error)
{
Clear();
std::ifstream File(Path,std::ios::binary);
if(!File){Error="cache file is missing or unreadable";return false;}
struct FHeader
{
char Magic[8];uint32 Version;uint32 Endian;uint64 SourceDataHash;
int32 Seed,ClipRadius,FullResClipLevels,CoarseTileCells,StrateContentCutMinLevel;
int32 SkirtCells,LODOctaveDrop,bGenerateSkirts;
uint32 AlgorithmVersion,TunnelMode,VoxelSizeCm;
uint64 TileCount;
};
static_assert(sizeof(FHeader)==80);
FHeader H{};File.read(reinterpret_cast<char*>(&H),sizeof(H));
const char ExpectedMagic[8]={'V','F','M','E','S','H','1','\0'};
if(!File||std::memcmp(H.Magic,ExpectedMagic,sizeof(ExpectedMagic))!=0
||H.Version!=1||H.Endian!=0x01020304u||H.TileCount>1000000)
{Error="mesh cache header is invalid";return false;}
if(H.SourceDataHash!=Expected.SourceDataHash||H.Seed!=Expected.Seed
||H.ClipRadius!=Expected.ClipRadius||H.FullResClipLevels!=Expected.FullResClipLevels
||H.CoarseTileCells!=Expected.CoarseTileCells
||H.StrateContentCutMinLevel!=Expected.StrateContentCutMinLevel
||H.SkirtCells!=Expected.SkirtCells||H.LODOctaveDrop!=Expected.LODOctaveDrop
||H.bGenerateSkirts!=Expected.bGenerateSkirts
||H.AlgorithmVersion!=Expected.AlgorithmVersion||H.TunnelMode!=Expected.TunnelMode
||H.VoxelSizeCm!=Expected.VoxelSizeCm)
{Error="mesh cache metadata does not match this world configuration";return false;}
struct FRecord
{
int32 Level,X,Y,Z;uint64 Samples,DensityHash;uint32 VertexCount,IndexCount;
};
static_assert(sizeof(FRecord)==40);
std::unordered_map<FVoxelTileKey,FStandaloneCpuTile,FStandaloneTileKeyHash> Loaded;
Loaded.reserve(static_cast<size_t>(H.TileCount)*2+1);
for(uint64 I=0;I<H.TileCount;++I)
{
FRecord Record{};File.read(reinterpret_cast<char*>(&Record),sizeof(Record));
if(!File||Record.Level<0||Record.Level>8||Record.VertexCount>10000000
||Record.IndexCount>60000000)
{Error="mesh cache contains an invalid tile record";return false;}
FStandaloneCpuTile Tile;Tile.Key=FVoxelTileKey(FIntVector(Record.X,Record.Y,Record.Z),Record.Level);
Tile.DensitySamples=Record.Samples;Tile.DensityHash=Record.DensityHash;Tile.bHasDensityHash=true;
Tile.Vertices.resize(Record.VertexCount);Tile.Indices.resize(Record.IndexCount);
if(!Tile.Vertices.empty())File.read(reinterpret_cast<char*>(Tile.Vertices.data()),
static_cast<std::streamsize>(Tile.Vertices.size()*sizeof(FStandaloneCpuVertex)));
if(!Tile.Indices.empty())File.read(reinterpret_cast<char*>(Tile.Indices.data()),
static_cast<std::streamsize>(Tile.Indices.size()*sizeof(uint32)));
if(!File){Error="mesh cache ended inside tile geometry";return false;}
for(const uint32 Index:Tile.Indices)
if(Index>=Tile.Vertices.size())
{Error="mesh cache contains an out-of-range mesh index";return false;}
for(const FStandaloneCpuVertex& Vertex:Tile.Vertices)
for(const float Value:Vertex.Position)
if(!std::isfinite(Value))
{Error="mesh cache contains a non-finite vertex";return false;}
for(const FStandaloneCpuVertex& Vertex:Tile.Vertices)
for(const float Value:Vertex.Normal)
if(!std::isfinite(Value))
{Error="mesh cache contains a non-finite normal";return false;}
if(!Loaded.emplace(Tile.Key,std::move(Tile)).second)
{Error="mesh cache contains duplicate tile keys";return false;}
}
const std::streamoff End=File.tellg();
if(End<0){Error="could not determine mesh cache size";return false;}
BytesLoaded=static_cast<uint64>(End);Tiles.swap(Loaded);bLoaded=true;
return true;
}
private:
std::unordered_map<FVoxelTileKey,FStandaloneCpuTile,FStandaloneTileKeyHash> Tiles;
bool bLoaded=false;
uint64 BytesLoaded=0;
};
struct FStandaloneStreamingUpdate
{
FIntVector CenterChunk=FIntVector::ZeroValue;
int32 DesiredTiles=0;
int32 RetainedResidentTiles=0;
int32 NewJobs=0;
int32 CancelledQueuedJobs=0;
int32 FullResTiles=0;
std::vector<std::pair<uint64,uint64>> CancelledByCrossing;
};
struct FStandaloneTileJob
{
FVoxelTileKey Key;
int32 BandZMin=MIN_int32;
int32 BandZMax=MAX_int32;
uint64 RequestedCrossing=0;
double PriorityDistanceSquared=0.0;
};
struct FStandaloneTileJobHeapOrder
{
bool operator()(const FStandaloneTileJob& A,const FStandaloneTileJob& B)const
{
if(A.PriorityDistanceSquared!=B.PriorityDistanceSquared)
return A.PriorityDistanceSquared>B.PriorityDistanceSquared;
if(A.Key.Level!=B.Key.Level)return A.Key.Level>B.Key.Level;
if(A.Key.Coord.Z!=B.Key.Coord.Z)return A.Key.Coord.Z>B.Key.Coord.Z;
if(A.Key.Coord.Y!=B.Key.Coord.Y)return A.Key.Coord.Y>B.Key.Coord.Y;
return A.Key.Coord.X>B.Key.Coord.X;
}
};
namespace StandaloneWorldMesher
{
inline double TileDistanceSquaredChunks(const FVoxelTileKey& Tile,const FIntVector& Center)
{
const double Scale=static_cast<double>(1<<Tile.Level);
const double DX=(static_cast<double>(Tile.Coord.X)+0.5)*Scale-Center.X;
const double DY=(static_cast<double>(Tile.Coord.Y)+0.5)*Scale-Center.Y;
const double DZ=(static_cast<double>(Tile.Coord.Z)+0.5)*Scale-Center.Z;
return DX*DX+DY*DY+DZ*DZ;
}
inline FStandaloneDesiredTileSet BuildDesiredSet(
const FStandaloneOwnerWorld& World,const FVector& CameraPositionVoxels)
{
FStandaloneDesiredTileSet Desired;
Desired.CenterChunk=CameraChunk(CameraPositionVoxels);
if(World.Settings->bClampViewToStrate)
Desired.bHasStrate=World.Manager->GetStrateChunkZBounds(
Desired.CenterChunk.Z,Desired.StrateTopChunkZ,Desired.StrateBottomChunkZ);
VoxelClipmapDesiredTiles::ResolveVerticalBounds(
Desired.CenterChunk.Z,World.Settings->ViewDistanceUp,World.Settings->ViewDistanceDown,
World.Settings->StrateViewMarginChunks,World.Settings->bClampViewToStrate,
Desired.bHasStrate,Desired.StrateTopChunkZ,Desired.StrateBottomChunkZ,
Desired.MinimumChunkZ,Desired.MaximumChunkZ);
VoxelClipmapDesiredTiles::FParameters Parameters;
Parameters.ClipRadius=World.ClipRadius;
Parameters.MaxClipLevel=FMath::Clamp(World.MaxClipLevel,0,8);
Parameters.RenderDistanceChunks=World.RenderDistanceChunks;
TArray<FVoxelTileKey> Tiles;
VoxelClipmapDesiredTiles::Build(Desired.CenterChunk,Desired.MinimumChunkZ,
Desired.MaximumChunkZ,Parameters,Tiles);
Desired.Tiles.reserve(static_cast<size_t>(Tiles.Num()));
for(const FVoxelTileKey& Tile:Tiles)Desired.Tiles.push_back(Tile);
std::sort(Desired.Tiles.begin(),Desired.Tiles.end(),[&](const FVoxelTileKey& A,const FVoxelTileKey& B)
{
const double DA=TileDistanceSquaredChunks(A,Desired.CenterChunk);
const double DB=TileDistanceSquaredChunks(B,Desired.CenterChunk);
return DA==DB?A.Level<B.Level:DA<DB;
});
return Desired;
}
}
class FStandaloneStreamingGenerator
{
public:
explicit FStandaloneStreamingGenerator(FStandaloneOwnerWorld& InWorld,int32 InWorkerCount=8,
bool bInHashDensity=false,
const FStandaloneMeshCache* InMeshCache=nullptr,
int32 InMaxLevelFilter=-1,
bool bInSkipUvAndColors=false,
bool bInSkipMaterialGroups=false,
bool bInSkipUV=false,
bool bInSkipColors=false)
:World(&InWorld),WorkerCount(FMath::Clamp(InWorkerCount,1,16)),bHashDensity(bInHashDensity),
MeshCache(InMeshCache),MaxLevelFilter(InMaxLevelFilter),
bSkipUvAndColors(bInSkipUvAndColors),bSkipMaterialGroups(bInSkipMaterialGroups),
bSkipUV(bInSkipUV),bSkipColors(bInSkipColors)
{
VoxelGenLOD::InitializeConsoleSwitches();
UVoxelMarchingCubesMesher::InitializeConsoleSwitches();
}
FStandaloneStreamingGenerator(const FStandaloneStreamingGenerator&)=delete;
FStandaloneStreamingGenerator& operator=(const FStandaloneStreamingGenerator&)=delete;
~FStandaloneStreamingGenerator(){Stop();}
FStandaloneStreamingUpdate Start(const FVector& CameraPositionVoxels,
const std::vector<FVoxelTileKey>& ResidentKeys={},
uint64 CrossingIndex=0)
{
Stop();
{
std::lock_guard<std::mutex> Lock(Mutex);
bStop=false;
}
for(int32 I=0;I<WorkerCount;++I)Workers.emplace_back([this](){WorkerLoop();});
return UpdateDesired(CameraPositionVoxels,ResidentKeys,CrossingIndex);
}
void SetDesiredTileFilter(const std::vector<FVoxelTileKey>& Keys)
{
TileFilter.clear();TileFilter.reserve(Keys.size()*2+1);
for(const FVoxelTileKey& Key:Keys)TileFilter.insert(Key);
bUseTileFilter=true;
}
void Stop()
{
{
std::lock_guard<std::mutex> Lock(Mutex);
bStop=true;
Queue.clear();
}
Wake.notify_all();
for(std::thread& Worker:Workers)if(Worker.joinable())Worker.join();
Workers.clear();
std::lock_guard<std::mutex> Lock(Mutex);
Ready.clear();Outstanding.clear();Desired.clear();
}
FStandaloneStreamingUpdate UpdateDesired(
const FVector& CameraPositionVoxels,const std::vector<FVoxelTileKey>& ResidentKeys,
uint64 CrossingIndex=0)
{
FStandaloneDesiredTileSet NewDesired=StandaloneWorldMesher::BuildDesiredSet(*World,CameraPositionVoxels);
if(MaxLevelFilter>=0)
NewDesired.Tiles.erase(std::remove_if(NewDesired.Tiles.begin(),NewDesired.Tiles.end(),
[&](const FVoxelTileKey& Key){return Key.Level>MaxLevelFilter;}),NewDesired.Tiles.end());
if(bUseTileFilter)
NewDesired.Tiles.erase(std::remove_if(NewDesired.Tiles.begin(),NewDesired.Tiles.end(),
[&](const FVoxelTileKey& Key){return !TileFilter.contains(Key);}),NewDesired.Tiles.end());
FStandaloneStreamingUpdate Stats;
Stats.CenterChunk=NewDesired.CenterChunk;
Stats.DesiredTiles=static_cast<int32>(NewDesired.Tiles.size());
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Resident;
Resident.reserve(ResidentKeys.size()*2+1);
for(const FVoxelTileKey& Key:ResidentKeys)Resident.insert(Key);
for(const FVoxelTileKey& Key:NewDesired.Tiles)
{
if(Key.Level<World->FullResClipLevels)++Stats.FullResTiles;
if(Resident.contains(Key))++Stats.RetainedResidentTiles;
}
std::lock_guard<std::mutex> Lock(Mutex);
Desired.clear();Desired.reserve(NewDesired.Tiles.size()*2+1);
for(const FVoxelTileKey& Key:NewDesired.Tiles)Desired.insert(Key);
FullResTileCount=Stats.FullResTiles;
std::unordered_map<uint64,uint64> CancelledByCrossing;
auto QueueEnd=std::remove_if(Queue.begin(),Queue.end(),[&](const FStandaloneTileJob& Job)
{
if(Desired.contains(Job.Key))return false;
Outstanding.erase(Job.Key);++Stats.CancelledQueuedJobs;
++CancelledByCrossing[Job.RequestedCrossing];return true;
});
Queue.erase(QueueEnd,Queue.end());
Stats.CancelledByCrossing.reserve(CancelledByCrossing.size());
for(const auto& Pair:CancelledByCrossing)Stats.CancelledByCrossing.push_back(Pair);
for(FStandaloneTileJob& Job:Queue)
Job.PriorityDistanceSquared=StandaloneWorldMesher::TileDistanceSquaredChunks(
Job.Key,NewDesired.CenterChunk);
const int32 BandMin=World->StrateContentCutMinLevel<9&&NewDesired.bHasStrate
? NewDesired.StrateBottomChunkZ*CHUNK_SIZE:MIN_int32;
const int32 BandMax=World->StrateContentCutMinLevel<9&&NewDesired.bHasStrate
? (NewDesired.StrateTopChunkZ+1)*CHUNK_SIZE:MAX_int32;
for(const FVoxelTileKey& Key:NewDesired.Tiles)
{
if(Resident.contains(Key)||Outstanding.contains(Key))continue;
Queue.push_back({Key,BandMin,BandMax,CrossingIndex,
StandaloneWorldMesher::TileDistanceSquaredChunks(Key,NewDesired.CenterChunk)});
Outstanding.insert(Key);++Stats.NewJobs;
}
CurrentDesired=std::move(NewDesired);
std::make_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
Wake.notify_all();
return Stats;
}
std::vector<FStandaloneCpuTile> TakeReady()
{
std::vector<FStandaloneCpuTile> Out;
std::lock_guard<std::mutex> Lock(Mutex);
Out.swap(Ready);
for(const FStandaloneCpuTile& Tile:Out)Outstanding.erase(Tile.Key);
return Out;
}
bool IsDesired(const FVoxelTileKey& Key)const
{std::lock_guard<std::mutex> Lock(Mutex);return Desired.contains(Key);}
int32 DesiredCount()const
{std::lock_guard<std::mutex> Lock(Mutex);return static_cast<int32>(Desired.size());}
int32 FullResDesiredCount()const
{std::lock_guard<std::mutex> Lock(Mutex);return FullResTileCount;}
FIntVector CenterChunk()const
{std::lock_guard<std::mutex> Lock(Mutex);return CurrentDesired.CenterChunk;}
int32 GetWorkerCount()const{return WorkerCount;}
uint64 GetCacheHits()const{return CacheHitCount.load(std::memory_order_relaxed);}
private:
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job)
{
const auto StartTime=FClock::now();
FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing;
if(MeshCache)
{
if(const FStandaloneCpuTile* Cached=MeshCache->Find(Job.Key))
{
Out=*Cached;Out.RequestedCrossing=Job.RequestedCrossing;
Out.WorkerSeconds=std::chrono::duration<double>(FClock::now()-StartTime).count();
Out.CompletedAt=FClock::now();
if(bHashDensity)
{Out.MeshHash=HashStandaloneCpuTileMesh(Out);Out.bHasMeshHash=true;}
CacheHitCount.fetch_add(1,std::memory_order_relaxed);
return Out;
}
}
UVoxelMarchingCubesMesher Mesher;
Mesher.SetGenerator(World->Generator.get());
Mesher.bStandaloneSkipUvAndColors=bSkipUvAndColors;
Mesher.bStandaloneSkipUV=bSkipUV;
Mesher.bStandaloneSkipColors=bSkipColors;
Mesher.bStandaloneSkipMaterialGroups=bSkipMaterialGroups;
Mesher.bGenerateSkirts=World->bGenerateSkirts;
Mesher.SkirtCells=static_cast<float>(World->SkirtCells);
Mesher.LODOctaveDrop=World->LODOctaveDrop;
const int32 Level=Job.Key.Level;
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);
const FStandaloneShapeWorld* Shape=FStandaloneShapeWorldAccess::GetActive();
int64 SampleCount=0;
const bool bProfile=VoxelDensityProfile::GetMode()!=VoxelDensityProfile::EMode::Disabled;
const VoxelDensityProfile::FSnapshot Before=bProfile
? VoxelDensityProfile::SnapshotCurrentThread():VoxelDensityProfile::FSnapshot{};
std::vector<float> DensityValues;
FVoxelMeshData Mesh=Mesher.GenerateMesh(Job.Key.OriginVoxels(),Step,Cells,nullptr,
Job.BandZMin,Job.BandZMax,&SampleCount,nullptr,nullptr,
bHashDensity?&DensityValues:nullptr);
if(Shape&&Shape->HasFarWorld())
{
AppendFarStratumShells(*World,*Shape,Job.Key,Step,Mesh);
AppendFarConnectorShells(*Shape,Job.Key,Step,World->CoarseTileCells,Mesh);
}
if(bHashDensity)
{
Out.bHasDensityHash=true;
for(const float Density:DensityValues)
{
Out.DensityHash^=std::bit_cast<uint32>(Density);
Out.DensityHash*=1099511628211ull;
}
}
if(bProfile)
{
const VoxelDensityProfile::FSnapshot After=VoxelDensityProfile::SnapshotCurrentThread();
for(size_t I=0;I<Out.ProfileCycles.size();++I)
{
const size_t Bucket=I;
Out.ProfileCycles[I]=After.Cycles[Bucket]>=Before.Cycles[Bucket]
?After.Cycles[Bucket]-Before.Cycles[Bucket]:0;
}
}
Out.DensitySamples=static_cast<uint64>(FMath::Max<int64>(0,SampleCount));
Out.MeshPayloadAllocatedBytes=static_cast<uint64>(Mesh.Vertices.GetAllocatedSize())
+static_cast<uint64>(Mesh.Normals.GetAllocatedSize())
+static_cast<uint64>(Mesh.UVs.GetAllocatedSize())
+static_cast<uint64>(Mesh.Colors.GetAllocatedSize())
+static_cast<uint64>(Mesh.Triangles.GetAllocatedSize());
if(bHashDensity)
{
Out.PayloadHash=HashStandaloneMeshPayload(Mesh);Out.bHasPayloadHash=true;
Out.TopologyHash=HashStandaloneMeshTopology(Mesh);Out.bHasTopologyHash=true;
}
Out.Vertices.reserve(static_cast<size_t>(Mesh.Vertices.Num()));
for(int32 I=0;I<Mesh.Vertices.Num();++I)
{
const FVector& P=Mesh.Vertices[I];const FVector& N=Mesh.Normals[I];
Out.Vertices.push_back({
{static_cast<float>(P.X*0.01),static_cast<float>(P.Y*0.01),static_cast<float>(P.Z*0.01)},
{static_cast<float>(N.X),static_cast<float>(N.Y),static_cast<float>(N.Z)}});
}
Out.Indices.reserve(static_cast<size_t>(Mesh.Triangles.Num()));
for(const int32 Index:Mesh.Triangles)Out.Indices.push_back(static_cast<uint32>(Index));
if(bHashDensity)
{Out.MeshHash=HashStandaloneCpuTileMesh(Out);Out.bHasMeshHash=true;}
Out.WorkerSeconds=std::chrono::duration<double>(FClock::now()-StartTime).count();
Out.CompletedAt=FClock::now();
return Out;
}
void WorkerLoop()
{
for(;;)
{
FStandaloneTileJob Job;
{
std::unique_lock<std::mutex> Lock(Mutex);
Wake.wait(Lock,[&](){return bStop||!Queue.empty();});
if(bStop)return;
std::pop_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
Job=Queue.back();Queue.pop_back();
}
FStandaloneCpuTile Tile=Generate(Job);
{
std::lock_guard<std::mutex> Lock(Mutex);
if(bStop)return;
Ready.push_back(std::move(Tile));
}
}
}
using FClock=std::chrono::steady_clock;
FStandaloneOwnerWorld* World=nullptr;
int32 WorkerCount=8;
bool bHashDensity=false;
const FStandaloneMeshCache* MeshCache=nullptr;
int32 MaxLevelFilter=-1;
bool bSkipUvAndColors=false;
bool bSkipMaterialGroups=false;
bool bSkipUV=false;
bool bSkipColors=false;
std::atomic<uint64> CacheHitCount{0};
mutable std::mutex Mutex;
std::condition_variable Wake;
bool bStop=true;
std::vector<std::thread> Workers;
std::vector<FStandaloneTileJob> Queue;
std::vector<FStandaloneCpuTile> Ready;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Desired;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Outstanding;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> TileFilter;
bool bUseTileFilter=false;
FStandaloneDesiredTileSet CurrentDesired;
int32 FullResTileCount=0;
};