#pragma once #include "WorldMesher.h" #include "StandaloneShapeWorld.h" #include #include #include #include #include #include #include #include #include struct FStandaloneTileKeyHash { std::size_t operator()(const FVoxelTileKey& Key) const noexcept { return static_cast(GetTypeHash(Key)); } }; struct FStandaloneDesiredTileSet { std::vector 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 Vertices; std::vector 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 ProfileCycles{}; }; inline uint64 HashStandaloneMeshPayload(const FVoxelMeshData& Mesh) { uint64 Hash=14695981039346656037ull; const auto AddWord=[&](uint64 Word,int32 Bytes) { for(int32 I=0;I(Word>>(I*8)); Hash*=1099511628211ull; } }; const auto AddDouble=[&](double Value) { AddWord(std::bit_cast(Value),8); }; AddWord(static_cast(Mesh.Vertices.Num()),8); for(const FVector& Position:Mesh.Vertices) { AddDouble(Position.X);AddDouble(Position.Y);AddDouble(Position.Z); } AddWord(static_cast(Mesh.Normals.Num()),8); for(const FVector& Normal:Mesh.Normals) { AddDouble(Normal.X);AddDouble(Normal.Y);AddDouble(Normal.Z); } AddWord(static_cast(Mesh.UVs.Num()),8); for(const auto& UV:Mesh.UVs) { AddDouble(static_cast(UV.X));AddDouble(static_cast(UV.Y)); } AddWord(static_cast(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(Mesh.Triangles.Num()),8); for(const int32 Index:Mesh.Triangles)AddWord(static_cast(Index),4); AddWord(static_cast(Mesh.NumSurfaceVertices),4); AddWord(static_cast(Mesh.NumSkirtTriangles),4); AddWord(static_cast(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(Word>>(I*8)); Hash*=1099511628211ull; } }; using FTriangleKey=std::array; const auto VertexKey=[&](int32 Index) { uint64 VertexHash=14695981039346656037ull; const auto AddVertexWord=[&](uint64 Word) { for(int32 Byte=0;Byte<8;++Byte) { VertexHash^=static_cast(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(static_cast(Position.X))); AddVertexWord(std::bit_cast(static_cast(Position.Y))); AddVertexWord(std::bit_cast(static_cast(Position.Z))); AddVertexWord(std::bit_cast(static_cast(Normal.X))); AddVertexWord(std::bit_cast(static_cast(Normal.Y))); AddVertexWord(std::bit_cast(static_cast(Normal.Z))); return VertexHash; }; std::vector Triangles; Triangles.reserve(static_cast(Mesh.Triangles.Num()/3)); for(int32 I=0;I+2(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(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(Value),4); for(const float Value:Vertex.Normal)AddWord(std::bit_cast(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(Cells)*Cells*Cells), TriangleMarks(static_cast(InMesh.Triangles.Num()/3),0) { for(int32 Triangle=0;Triangle+2=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::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(Triangle)]==QueryStamp)continue; TriangleMarks[static_cast(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::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<(std::floor((Value-(&Origin.X)[Axis])/Step)),0,Cells-1);} size_t CellOffset(int32 X,int32 Y,int32 Z)const {return (static_cast(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> ByCell; std::vector 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(SearchStart.X),static_cast(SearchStart.Y), static_cast(SearchStart.Z))<0.0f; double BestDifference=std::numeric_limits::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(P.X),static_cast(P.Y), static_cast(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(M.X),static_cast(M.Y), static_cast(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=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(Center.X+Direction.X*LocalPrevious), static_cast(Center.Y+Direction.Y*LocalPrevious), static_cast(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(P.X),static_cast(P.Y), static_cast(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(M.X),static_cast(M.Y), static_cast(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=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(LoPoint.X),static_cast(LoPoint.Y), static_cast(LoPoint.Z))<0.0f; const bool HiSolid=Shape.DensityAt(static_cast(HiPoint.X),static_cast(HiPoint.Y), static_cast(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 MeshIndex; std::vector NewVertices; std::vector NewNormals; std::vector NewUVs; std::vector NewColors; std::vector 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 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)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]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(std::ceil(Length/128.0))); std::vector MissingMasks(static_cast(AxialSampleCount),0); std::vector> SourceRadii(static_cast(AxialSampleCount)); for(auto& Radii:SourceRadii)Radii.fill(0.0); bool bAnyMissing=false; bool bHasAuditSection=false; for(int32 Sample=0;Sample(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(Connector.Radius)*0.75); const double MatchTolerance=std::max(8.0,static_cast(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(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(Sample)][DirectionIndex]=SourceRadius; if(bMissing) { MissingMasks[static_cast(Sample)]|=static_cast(1u< 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<(Sample)][DirectionIndex]; if(Known>0.0){Radius=Known;break;} } } Ring.Radii[DirectionIndex]=Radius; } return Ring; }; int32 RunStart=0; while(RunStart(RunStart)]){++RunStart;continue;} int32 RunEnd=RunStart; while(RunEnd+1(RunEnd+1)])++RunEnd; const double RunMin=std::max(TMin,static_cast(RunStart)/AxialSampleCount); const double RunMax=std::min(TMax,static_cast(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(Sample)]; for(int32 DirectionIndex=0;DirectionIndex<4;++DirectionIndex) { if((Mask&(1u<((1u<<0)|(1u<<2));break; case 1:RunRadiusMask|=static_cast((1u<<1)|(1u<<2));break; case 2:RunRadiusMask|=static_cast((1u<<1)|(1u<<3));break; default:RunRadiusMask|=static_cast((1u<<0)|(1u<<3));break; } } } std::vector Rings; const double MaxRingStep=128.0; double CurrentT=RunMin; Rings.push_back(MakeRing(CurrentT,RunStart,RunEnd,RunRadiusMask)); while(CurrentT(Sample)]; if(!Mask)continue; const double SampleT=(static_cast(Sample)+0.5)/AxialSampleCount; auto RightIt=std::lower_bound(Rings.begin(),Rings.end(),SampleT, [](const FRing& Ring,double T){return Ring.TT-SampleT)<=1.0e-7) { for(int32 DirectionIndex=0;DirectionIndex<4;++DirectionIndex) { if((Mask&(1u<(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<(Sample)][DirectionIndex]; const double Predicted=LeftRing.Radii[DirectionIndex]*(1.0-Alpha) +RightRing.Radii[DirectionIndex]*Alpha; const double Tolerance=std::max(8.0,static_cast(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<(Sample)][DirectionIndex]; if(Exact>0.0)ExactRing.Radii[DirectionIndex]=Exact; } Rings.insert(RightIt,std::move(ExactRing)); bAddedSampleRing=true; } } if(Rings.size()>=2) { std::vector IntervalMasks(Rings.size()-1,0); std::vector> NeededSides(Rings.size()); for(auto& Sides:NeededSides)Sides.fill(0); for(int32 Sample=RunStart;Sample<=RunEnd;++Sample) { const uint8 Mask=MissingMasks[static_cast(Sample)]; if(!Mask)continue; const double SampleT=(static_cast(Sample)+0.5)/AxialSampleCount; const auto Lower=std::lower_bound(Rings.begin(),Rings.end(),SampleT, [](const FRing& Ring,double T){return Ring.T(Lower-Rings.begin()); if(Right0)IntervalMasks[Right-1]|=Mask; if(Right+10&&Right> RingVertexIndex(Rings.size()); for(auto& Indices:RingVertexIndex)Indices.fill(INDEX_NONE); for(size_t Ring=0;Ring(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(Rings[Ring].T*Length/256.0), static_cast(Side)/SideCount); if(bHasColors)NewColors.push_back(Color); } } for(size_t Ring=0;Ring+1(1u<(NewVertices.size()); auto InsertVertices=[&](auto& Existing,const auto& Added) { using TArrayType=std::remove_reference_t; TArrayType Combined;Combined.Reserve(Existing.Num()+static_cast(Added.size())); for(int32 I=0;I=OldSurfaceVertices)Index+=AddedVertices; const int32 CeilingStart=std::max(0,Mesh.Triangles.Num()-Mesh.NumCeilingTriangles*3); TArray CombinedTriangles;CombinedTriangles.Reserve(Mesh.Triangles.Num()+static_cast(NewTriangles.size())); for(int32 I=0;I(InCells)*InCells) { for(int32 Triangle=0;Triangle+2=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(std::floor((MinX-Origin.X)/Step)),0,Cells-1); const int32 X1=std::clamp(static_cast(std::floor((MaxX-Origin.X)/Step)),0,Cells-1); const int32 Y0=std::clamp(static_cast(std::floor((MinY-Origin.Y)/Step)),0,Cells-1); const int32 Y1=std::clamp(static_cast(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(Y)*Cells+X].push_back(Triangle/3); } } std::vector VerticalHits(double X,double Y,double ZMin,double ZMax) const { const int32 CX=std::clamp(static_cast(std::floor((X-Origin.X)/Step)),0,Cells-1); const int32 CY=std::clamp(static_cast(std::floor((Y-Origin.Y)/Step)),0,Cells-1); const auto& Candidates=ByColumn[static_cast(CY)*Cells+CX]; std::vector 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> ByColumn; }; inline std::vector FindFarStratumCrossings(const FStandaloneShapeWorld& Shape, double X,double Y,double ZMin,double ZMax) { std::vector Crossings; if(ZMax<=ZMin)return Crossings; double PreviousZ=ZMin; bool PreviousSolid=Shape.DensityAt(static_cast(X),static_cast(Y),static_cast(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(X),static_cast(Y),static_cast(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(X),static_cast(Y),static_cast(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 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(Params.BoundarySealThickness)+8.0); Boundaries.push_back({static_cast(Slot.BottomChunkZ)*CHUNK_SIZE,HalfWidth}); Boundaries.push_back({static_cast(Slot.TopChunkZ+1)*CHUNK_SIZE,HalfWidth}); } std::sort(Boundaries.begin(),Boundaries.end(),[](const FBoundary& A,const FBoundary& B){return A.Z 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 NewVertices,NewNormals; std::vector NewUVs;std::vector NewColors; std::vector NewTriangles; static constexpr int32 ColumnOffsets[4]={1,5,9,13}; const auto Density=[&](double X,double Y,double Z) {return Shape.DensityAt(static_cast(X),static_cast(Y),static_cast(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 Source=FindFarStratumCrossings(Shape,X,Y,ZMin,ZMax); if(Source.empty())continue; std::vector Hits=MeshIndex.VerticalHits(X,Y,ZMin,ZMax); std::vector 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::infinity(); for(size_t I=0;I(Origin.X,X-HalfSize),X1=std::min(Origin.X+Extent,X+HalfSize); const double Y0=std::max(Origin.Y,Y-HalfSize),Y1=std::min(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(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((Xs[Corner]-Origin.X)/256.0), static_cast((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(NewVertices.size()); auto InsertVertices=[&](auto& Existing,const auto& Added) { using TArrayType=std::remove_reference_t; TArrayType Combined;Combined.Reserve(Existing.Num()+static_cast(Added.size())); for(int32 I=0;I=OldSurfaceVertices)Index+=AddedVertices; const int32 CeilingStart=std::max(0,Mesh.Triangles.Num()-Mesh.NumCeilingTriangles*3); TArray CombinedTriangles;CombinedTriangles.Reserve(Mesh.Triangles.Num()+static_cast(NewTriangles.size())); for(int32 I=0;Isecond; } static bool Save(const std::string& Path,const FStandaloneMeshCacheMetadata& Metadata, std::vector& InTiles,std::string& Error) { std::vector 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.LevelKey.Level; if(A->Key.Coord.Z!=B->Key.Coord.Z)return A->Key.Coord.ZKey.Coord.Z; if(A->Key.Coord.Y!=B->Key.Coord.Y)return A->Key.Coord.YKey.Coord.Y; return A->Key.Coord.XKey.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(Ordered.size())}; File.write(reinterpret_cast(&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(Tile->Vertices.size()),static_cast(Tile->Indices.size())}; File.write(reinterpret_cast(&Record),sizeof(Record)); if(!Tile->Vertices.empty())File.write(reinterpret_cast(Tile->Vertices.data()), static_cast(Tile->Vertices.size()*sizeof(FStandaloneCpuVertex))); if(!Tile->Indices.empty())File.write(reinterpret_cast(Tile->Indices.data()), static_cast(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(&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 Loaded; Loaded.reserve(static_cast(H.TileCount)*2+1); for(uint64 I=0;I(&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(Tile.Vertices.data()), static_cast(Tile.Vertices.size()*sizeof(FStandaloneCpuVertex))); if(!Tile.Indices.empty())File.read(reinterpret_cast(Tile.Indices.data()), static_cast(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(End);Tiles.swap(Loaded);bLoaded=true; return true; } private: std::unordered_map 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> 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(1<(Tile.Coord.X)+0.5)*Scale-Center.X; const double DY=(static_cast(Tile.Coord.Y)+0.5)*Scale-Center.Y; const double DZ=(static_cast(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 Tiles; VoxelClipmapDesiredTiles::Build(Desired.CenterChunk,Desired.MinimumChunkZ, Desired.MaximumChunkZ,Parameters,Tiles); Desired.Tiles.reserve(static_cast(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& ResidentKeys={}, uint64 CrossingIndex=0) { Stop(); { std::lock_guard Lock(Mutex); bStop=false; } for(int32 I=0;I& Keys) { TileFilter.clear();TileFilter.reserve(Keys.size()*2+1); for(const FVoxelTileKey& Key:Keys)TileFilter.insert(Key); bUseTileFilter=true; } void Stop() { { std::lock_guard Lock(Mutex); bStop=true; Queue.clear(); } Wake.notify_all(); for(std::thread& Worker:Workers)if(Worker.joinable())Worker.join(); Workers.clear(); std::lock_guard Lock(Mutex); Ready.clear();Outstanding.clear();Desired.clear(); } FStandaloneStreamingUpdate UpdateDesired( const FVector& CameraPositionVoxels,const std::vector& 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(NewDesired.Tiles.size()); std::unordered_set Resident; Resident.reserve(ResidentKeys.size()*2+1); for(const FVoxelTileKey& Key:ResidentKeys)Resident.insert(Key); for(const FVoxelTileKey& Key:NewDesired.Tiles) { if(Key.LevelFullResClipLevels)++Stats.FullResTiles; if(Resident.contains(Key))++Stats.RetainedResidentTiles; } std::lock_guard 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 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 TakeReady() { std::vector Out; std::lock_guard 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 Lock(Mutex);return Desired.contains(Key);} int32 DesiredCount()const {std::lock_guard Lock(Mutex);return static_cast(Desired.size());} int32 FullResDesiredCount()const {std::lock_guard Lock(Mutex);return FullResTileCount;} FIntVector CenterChunk()const {std::lock_guard 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(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(World->SkirtCells); Mesher.LODOctaveDrop=World->LODOctaveDrop; const int32 Level=Job.Key.Level; const int32 Extent=Job.Key.ExtentVoxels(); const int32 Cells=LevelFullResClipLevels?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 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(Density); Out.DensityHash*=1099511628211ull; } } if(bProfile) { const VoxelDensityProfile::FSnapshot After=VoxelDensityProfile::SnapshotCurrentThread(); for(size_t I=0;I=Before.Cycles[Bucket] ?After.Cycles[Bucket]-Before.Cycles[Bucket]:0; } } Out.DensitySamples=static_cast(FMath::Max(0,SampleCount)); Out.MeshPayloadAllocatedBytes=static_cast(Mesh.Vertices.GetAllocatedSize()) +static_cast(Mesh.Normals.GetAllocatedSize()) +static_cast(Mesh.UVs.GetAllocatedSize()) +static_cast(Mesh.Colors.GetAllocatedSize()) +static_cast(Mesh.Triangles.GetAllocatedSize()); if(bHashDensity) { Out.PayloadHash=HashStandaloneMeshPayload(Mesh);Out.bHasPayloadHash=true; Out.TopologyHash=HashStandaloneMeshTopology(Mesh);Out.bHasTopologyHash=true; } Out.Vertices.reserve(static_cast(Mesh.Vertices.Num())); for(int32 I=0;I(P.X*0.01),static_cast(P.Y*0.01),static_cast(P.Z*0.01)}, {static_cast(N.X),static_cast(N.Y),static_cast(N.Z)}}); } Out.Indices.reserve(static_cast(Mesh.Triangles.Num())); for(const int32 Index:Mesh.Triangles)Out.Indices.push_back(static_cast(Index)); if(bHashDensity) {Out.MeshHash=HashStandaloneCpuTileMesh(Out);Out.bHasMeshHash=true;} Out.WorkerSeconds=std::chrono::duration(FClock::now()-StartTime).count(); Out.CompletedAt=FClock::now(); return Out; } void WorkerLoop() { for(;;) { FStandaloneTileJob Job; { std::unique_lock 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 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 CacheHitCount{0}; mutable std::mutex Mutex; std::condition_variable Wake; bool bStop=true; std::vector Workers; std::vector Queue; std::vector Ready; std::unordered_set Desired; std::unordered_set Outstanding; std::unordered_set TileFilter; bool bUseTileFilter=false; FStandaloneDesiredTileSet CurrentDesired; int32 FullResTileCount=0; };