wip(assembler): round S3 -- far ground from the native heightfield; LOD7/8 tiles 36-66% cheaper

Far-shell repair (81-96% of S2's LOD7/8 tile time) is skipped for certified
SurfaceWorld tiles; ground and cap come from the shared planar heightfield.
Near LOD0-3 hashes equal S2; far recipe version 1 salts LOD5+ identities.
Cold first view 6.6 -> 3.4 s (aim 1.57 s). Not a visual sign-off: distant cap
shows floating pieces, sprint streaming arrives late.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-18 21:29:52 +02:00
1 parent 6ddb834b1c
commit d0f97bf79a
26 files changed
+976 -30

No files matched your search

@@ -128,6 +128,17 @@ namespace VoxelForgeCore
double meshingMilliseconds = 0.0;
double featureListMilliseconds = 0.0;
double farShellMilliseconds = 0.0;
double farStratumShellMilliseconds = 0.0;
double farConnectorShellMilliseconds = 0.0;
double farHorizonMilliseconds = 0.0;
std::uint64_t farStratumFieldCalls = 0;
std::uint64_t farConnectorFieldCalls = 0;
std::uint64_t farShellTrianglesAdded = 0;
std::uint64_t horizonGroundTriangles = 0;
std::uint64_t horizonGroundTrianglesRemoved = 0;
std::uint64_t horizonCapTriangles = 0;
std::uint32_t farHorizonVersion = 0;
bool farShellRepairSkipped = false;
bool tileWasClassified = false;
bool meshingSkipped = false;
bool featureRefinedResolution = false;
@@ -984,6 +995,11 @@ namespace VoxelForgeCore
// Validation control only; never participates in recipe/world identity.
// Controle de validation seulement, sans effet sur l'identite du monde.
bool useAssemblyFeatureQueries = true;
// Bump when the far-horizon mesh representation changes. Far tile mesh hashes are
// salted with this version; density and LOD0-3 hashes are intentionally unchanged.
// Incremente quand la representation d'horizon lointain change; les hashes de maillage
// lointain incluent cette version, sans toucher a la densite ni aux LOD0-3.
std::uint32_t farHorizonVersion = 1;
};
class VFCORE_API World
+69 -8
View File
@@ -311,7 +311,10 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
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)));
// LOD7 and LOD8 share a 256-voxel connector station grid; near LOD keeps 128-voxel checks.
// LOD7 et LOD8 partagent des stations aux 256 voxels; pres du joueur, garder les pas de 128.
const double ConnectorStationStep=Key.Level>=7?std::max(256.0,static_cast<double>(Step)):128.0;
const int32 AxialSampleCount=std::max(1,static_cast<int32>(std::ceil(Length/ConnectorStationStep)));
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);
@@ -429,7 +432,7 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
}
}
std::vector<FRing> Rings;
const double MaxRingStep=128.0;
const double MaxRingStep=Key.Level>=7?std::max(256.0,static_cast<double>(Step)):128.0;
double CurrentT=RunMin;
Rings.push_back(MakeRing(CurrentT,RunStart,RunEnd,RunRadiusMask));
while(CurrentT<RunMax-1.0e-7)
@@ -901,6 +904,67 @@ inline std::vector<double> FindFarStratumCrossings(const FStandaloneShapeWorld&
return Crossings;
}
inline bool FindNearestFarStratumCrossing(const FStandaloneShapeWorld& Shape,
double X,double Y,double ZMin,double ZMax,double Prediction,double& OutCrossing)
{
if(ZMax<=ZMin||!std::isfinite(Prediction))return false;
const int32 Center=std::clamp(static_cast<int32>(std::floor(Prediction)),
static_cast<int32>(std::ceil(ZMin)),static_cast<int32>(std::floor(ZMax)));
double PreviousLeft=Center,PreviousRight=Center;
bool PreviousLeftSolid=Shape.DensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(PreviousLeft))<0.0f;
bool PreviousRightSolid=PreviousLeftSolid;
const int32 MaximumRadius=std::max(Center-static_cast<int32>(std::floor(ZMin)),
static_cast<int32>(std::ceil(ZMax))-Center)+1;
for(int32 Radius=1;Radius<=MaximumRadius;++Radius)
{
bool bFound=false;
double Best=0.0,BestDistance=std::numeric_limits<double>::infinity();
const auto CheckBracket=[&](double A,double B,bool ASolid)
{
double Lo=std::min(A,B),Hi=std::max(A,B);
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)==ASolid)
{
if(A<B)Lo=Mid;else Hi=Mid;
}
else
{
if(A<B)Hi=Mid;else Lo=Mid;
}
}
const double Crossing=(Lo+Hi)*0.5;
const double Difference=std::abs(Crossing-Prediction);
if(Difference<BestDistance)
{Best=Crossing;BestDistance=Difference;bFound=true;}
};
const double Left=std::max(ZMin,static_cast<double>(Center-Radius));
if(Left<PreviousLeft-1.0e-9)
{
const bool LeftSolid=Shape.DensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Left))<0.0f;
if(LeftSolid!=PreviousLeftSolid)CheckBracket(Left,PreviousLeft,LeftSolid);
PreviousLeft=Left;PreviousLeftSolid=LeftSolid;
}
const double Right=std::min(ZMax,static_cast<double>(Center+Radius));
if(Right>PreviousRight+1.0e-9)
{
const bool RightSolid=Shape.DensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Right))<0.0f;
if(RightSolid!=PreviousRightSolid)CheckBracket(PreviousRight,Right,PreviousRightSolid);
PreviousRight=Right;PreviousRightSolid=RightSolid;
}
if(bFound)
{OutCrossing=Best;return true;}
if(Left<=ZMin+1.0e-9&&Right>=ZMax-1.0e-9)break;
}
return false;
}
inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FStandaloneShapeWorld& Shape,
const FVoxelTileKey& Key,int32 Step,FVoxelMeshData& Mesh)
{
@@ -992,12 +1056,9 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
Vertex.X=X0+(X1-X0)*static_cast<double>(PX)/PatchDivisions;
Vertex.Y=Y0+(Y1-Y0)*static_cast<double>(PY)/PatchDivisions;
const double PredictedZ=SurfaceZ(Vertex.X,Vertex.Y);
const std::vector<double> Crossings=FindFarStratumCrossings(
Shape,Vertex.X,Vertex.Y,ZMin,ZMax);
if(Crossings.empty()){bValidPatch=false;break;}
Vertex.Z=*std::min_element(Crossings.begin(),Crossings.end(),
[PredictedZ](double A,double B)
{return std::abs(A-PredictedZ)<std::abs(B-PredictedZ);});
if(!FindNearestFarStratumCrossing(Shape,Vertex.X,Vertex.Y,
ZMin,ZMax,PredictedZ,Vertex.Z))
{bValidPatch=false;break;}
const float VertexGX=Density(Vertex.X+2,Vertex.Y,Vertex.Z)
-Density(Vertex.X-2,Vertex.Y,Vertex.Z);
const float VertexGY=Density(Vertex.X,Vertex.Y+2,Vertex.Z)
+365 -17
View File
@@ -210,6 +210,13 @@ namespace VoxelForgeCore
&&std::isfinite(brush.strength)&&std::isfinite(brush.falloff)&&brush.falloff>=0.0f;
}
std::uint64_t SaltFarHorizonHash(std::uint64_t Hash,std::uint32_t Version)
{
AddWord(Hash,0x464152484f52495aull,8);
AddWord(Hash,Version,4);
return Hash;
}
FStandaloneShapeWorld::EDetailVariant ToNative(DetailVariant value)
{
switch(value)
@@ -226,6 +233,7 @@ namespace VoxelForgeCore
double YSub = 0.0;
double ZSub = 0.0;
double GroundSub = 0.0;
double CapSub = 0.0;
FVector Normal = FVector(0.0,0.0,-1.0);
FColor Color = FColor::White;
};
@@ -234,6 +242,7 @@ namespace VoxelForgeCore
{
std::uint64_t Cells = 0;
std::uint64_t Triangles = 0;
std::uint64_t RemovedTriangles = 0;
bool Refined = false;
};
@@ -252,6 +261,7 @@ namespace VoxelForgeCore
Out.YSub=A.YSub+(B.YSub-A.YSub)*T;
Out.ZSub=A.ZSub+(B.ZSub-A.ZSub)*T;
Out.GroundSub=A.GroundSub+(B.GroundSub-A.GroundSub)*T;
Out.CapSub=A.CapSub+(B.CapSub-A.CapSub)*T;
Out.Normal=NormalizeCapNormal(A.Normal+(B.Normal-A.Normal)*T);
const auto LerpByte=[T](uint8 AValue,uint8 BValue)
{return static_cast<uint8>(std::clamp(std::lround(AValue+(BValue-AValue)*T),0l,255l));};
@@ -287,11 +297,286 @@ namespace VoxelForgeCore
return OutputCount;
}
FNativeCapRefinementStats RefineLod8NativeCap(const FStandaloneOwnerWorld& Owner,
FNativeCapRefinementStats RefineFarNativeGround(const FStandaloneOwnerWorld& Owner,
const WorldAssemblyManifest& Manifest,const FVoxelTileKey& Key,FVoxelMeshData& Mesh)
{
FNativeCapRefinementStats Stats;
if(Key.Level<8||!Owner.Generator)return Stats;
if(Key.Level<5||!Owner.Generator)return Stats;
const int32 GridStepVoxels=std::min(128,1<<Key.Level);
constexpr int64 Scale=WorldAssemblySubvoxelsPerVoxel;
const int64 GridStepSubvoxels=static_cast<int64>(GridStepVoxels)*Scale;
const FIntVector TileOrigin=Key.OriginVoxels();
const int32 TileExtent=Key.ExtentVoxels();
if(TileExtent<=0||TileExtent%GridStepVoxels!=0)return Stats;
const int32 Cells=TileExtent/GridStepVoxels;
const double TileMinZ=static_cast<double>(TileOrigin.Z)*Scale;
const double TileMaxZ=static_cast<double>(TileOrigin.Z+TileExtent)*Scale;
const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,Mesh.Vertices.Num());
const bool bHasUVs=Mesh.UVs.Num()==Mesh.Vertices.Num();
const bool bHasColors=Mesh.Colors.Num()==Mesh.Vertices.Num();
std::vector<FVector> AddedVertices,AddedNormals;
std::vector<FVector2f> AddedUVs;
std::vector<FColor> AddedColors;
std::vector<int32> AddedTriangles;
const int32 OldCeilingTriangleCount=std::max(0,Mesh.NumCeilingTriangles);
const int32 OldCeilingStart=std::max(0,Mesh.Triangles.Num()-OldCeilingTriangleCount*3);
struct FGroundProfile
{
int32 Cells=0,GridStep=0;
double MinimumZ=0.0,MaximumZ=0.0;
std::vector<double> Ground,Cap;
};
std::vector<FGroundProfile> GroundProfiles;
for(const auto& Plan:Manifest.plan.strates)
{
const int64 StratumMinZ=static_cast<int64>(Plan.bottomChunkZ)*CHUNK_SIZE*Scale;
const int64 StratumMaxZ=(static_cast<int64>(Plan.topChunkZ)+1)*CHUNK_SIZE*Scale;
if(TileMaxZ<StratumMinZ||TileMinZ>StratumMaxZ)continue;
const VoxelForgeCore::Heightfield2D Block(Manifest.recipe,Plan);
if(!Block.isValid())continue;
struct FGridSample
{
double Ground=0.0,Cap=0.0;
FVector Normal=FVector(0.0,0.0,1.0);
FColor Color=FColor::White;
int32 VertexIndex=INDEX_NONE;
};
std::vector<FGridSample> Grid(static_cast<std::size_t>(Cells+1)*(Cells+1));
const auto GridIndex=[Cells](int32 X,int32 Y)
{return static_cast<std::size_t>(Y)*(Cells+1)+X;};
for(int32 Y=0;Y<=Cells;++Y)
for(int32 X=0;X<=Cells;++X)
{
const int64 XSub=(static_cast<int64>(TileOrigin.X)+static_cast<int64>(X)*GridStepVoxels)*Scale;
const int64 YSub=(static_cast<int64>(TileOrigin.Y)+static_cast<int64>(Y)*GridStepVoxels)*Scale;
const auto Column=Block.point(XSub,YSub,0);
auto& Sample=Grid[GridIndex(X,Y)];
Sample.Ground=static_cast<double>(Column.groundHeightSubvoxels);
Sample.Cap=static_cast<double>(Column.capHeightSubvoxels);
int32 Dominant=0,Neighbor=0;float Blend=0.0f;
Owner.Generator->GetBiomeMaterialAt(static_cast<float>(XSub)/Scale,
static_cast<float>(YSub)/Scale,static_cast<float>(Sample.Ground/Scale),
Dominant,Neighbor,Blend);
Sample.Color=FColor(static_cast<uint8>(std::clamp(Dominant,0,255)),0,
static_cast<uint8>(std::clamp(std::lround(Blend*255.0f),0l,255l)),
static_cast<uint8>(std::clamp(Neighbor,0,255)));
}
for(int32 Y=0;Y<=Cells;++Y)
for(int32 X=0;X<=Cells;++X)
{
const int32 Left=std::max(0,X-1),Right=std::min(Cells,X+1);
const int32 Down=std::max(0,Y-1),Up=std::min(Cells,Y+1);
const double DX=static_cast<double>(Right-Left)*GridStepSubvoxels;
const double DY=static_cast<double>(Up-Down)*GridStepSubvoxels;
const double SlopeX=DX>0.0?(Grid[GridIndex(Right,Y)].Ground
-Grid[GridIndex(Left,Y)].Ground)/DX:0.0;
const double SlopeY=DY>0.0?(Grid[GridIndex(X,Up)].Ground
-Grid[GridIndex(X,Down)].Ground)/DY:0.0;
auto& Sample=Grid[GridIndex(X,Y)];
Sample.Normal=NormalizeCapNormal(FVector(-SlopeX,-SlopeY,1.0));
Sample.Color.G=static_cast<uint8>(std::clamp(
std::lround((1.0-std::abs(Sample.Normal.Z))*255.0),0l,255l));
}
FGroundProfile Profile;
Profile.Cells=Cells;Profile.GridStep=GridStepVoxels;
Profile.MinimumZ=static_cast<double>(std::min(Plan.bottomChunkZ,Plan.topChunkZ))*CHUNK_SIZE;
Profile.MaximumZ=static_cast<double>(std::max(Plan.bottomChunkZ,Plan.topChunkZ)+1)*CHUNK_SIZE;
Profile.Ground.reserve(Grid.size());Profile.Cap.reserve(Grid.size());
for(const auto& Sample:Grid)
{Profile.Ground.push_back(Sample.Ground/Scale);Profile.Cap.push_back(Sample.Cap/Scale);}
GroundProfiles.push_back(std::move(Profile));
// Partager les sommets du profil entre cellules reduit le payload d'horizon.
// Share regular profile vertices across adjacent cells to bound horizon payload.
for(int32 Y=0;Y<=Cells;++Y)
for(int32 X=0;X<=Cells;++X)
{
auto& Sample=Grid[GridIndex(X,Y)];
if(Sample.Ground>=Sample.Cap||Sample.Ground<TileMinZ||Sample.Ground>TileMaxZ)continue;
const int64 XSub=(static_cast<int64>(TileOrigin.X)+static_cast<int64>(X)*GridStepVoxels)*Scale;
const int64 YSub=(static_cast<int64>(TileOrigin.Y)+static_cast<int64>(Y)*GridStepVoxels)*Scale;
const double VoxelSizeCm=static_cast<double>(Owner.CoreVoxelSizeMicrons)/10000.0;
Sample.VertexIndex=static_cast<int32>(AddedVertices.size());
AddedVertices.emplace_back(static_cast<double>(TileOrigin.X+X*GridStepVoxels)*VoxelSizeCm,
static_cast<double>(TileOrigin.Y+Y*GridStepVoxels)*VoxelSizeCm,
Sample.Ground/Scale*VoxelSizeCm);
AddedNormals.push_back(Sample.Normal);
if(bHasUVs)AddedUVs.emplace_back(static_cast<float>(XSub/Scale),static_cast<float>(YSub/Scale));
if(bHasColors)AddedColors.push_back(Sample.Color);
}
for(int32 Y=0;Y<Cells;++Y)
for(int32 X=0;X<Cells;++X)
{
const int32 CellX[4]={X,X+1,X+1,X};
const int32 CellY[4]={Y,Y,Y+1,Y+1};
std::array<int32,4> GridVertices{};
bool bCompleteCell=true;
for(int32 Corner=0;Corner<4;++Corner)
{
GridVertices[Corner]=Grid[GridIndex(CellX[Corner],CellY[Corner])].VertexIndex;
bCompleteCell=bCompleteCell&&GridVertices[Corner]!=INDEX_NONE;
}
if(bCompleteCell)
{
AddedTriangles.insert(AddedTriangles.end(),{GridVertices[0],GridVertices[1],GridVertices[2],
GridVertices[0],GridVertices[2],GridVertices[3]});
++Stats.Cells;Stats.Triangles+=2;
continue;
}
std::array<FNativeCapVertex,16> PolygonA{},PolygonB{};
for(int32 Corner=0;Corner<4;++Corner)
{
const int32 GX=CellX[Corner],GY=CellY[Corner];
const auto& Sample=Grid[GridIndex(GX,GY)];
PolygonA[Corner].XSub=static_cast<double>(TileOrigin.X+GX*GridStepVoxels)*Scale;
PolygonA[Corner].YSub=static_cast<double>(TileOrigin.Y+GY*GridStepVoxels)*Scale;
PolygonA[Corner].GroundSub=Sample.Ground;
PolygonA[Corner].CapSub=Sample.Cap;
PolygonA[Corner].ZSub=Sample.Ground;
PolygonA[Corner].Normal=Sample.Normal;
PolygonA[Corner].Color=Sample.Color;
}
int32 Count=ClipNativeCapPolygon(PolygonA.data(),4,PolygonB.data(),
[](const auto& V){return V.CapSub-V.ZSub;});
Count=ClipNativeCapPolygon(PolygonB.data(),Count,PolygonA.data(),
[TileMinZ](const auto& V){return V.ZSub-TileMinZ;});
Count=ClipNativeCapPolygon(PolygonA.data(),Count,PolygonB.data(),
[TileMaxZ](const auto& V){return TileMaxZ-V.ZSub;});
if(Count<3)continue;
double TwiceArea=0.0;
for(int32 I=0;I<Count;++I)
{
const auto& A=PolygonB[I];const auto& B=PolygonB[(I+1)%Count];
TwiceArea+=A.XSub*B.YSub-B.XSub*A.YSub;
}
if(std::abs(TwiceArea)<1.0e-5)continue;
++Stats.Cells;
const int32 Base=static_cast<int32>(AddedVertices.size());
for(int32 I=0;I<Count;++I)
{
const auto& V=PolygonB[I];
const double VoxelSizeCm=static_cast<double>(Owner.CoreVoxelSizeMicrons)/10000.0;
AddedVertices.emplace_back(V.XSub/Scale*VoxelSizeCm,
V.YSub/Scale*VoxelSizeCm,V.ZSub/Scale*VoxelSizeCm);
AddedNormals.push_back(V.Normal);
if(bHasUVs)AddedUVs.emplace_back(static_cast<float>(V.XSub/Scale),
static_cast<float>(V.YSub/Scale));
if(bHasColors)AddedColors.push_back(V.Color);
}
for(int32 Fan=1;Fan+1<Count;++Fan)
{
AddedTriangles.insert(AddedTriangles.end(),{Base,Base+Fan,Base+Fan+1});
++Stats.Triangles;
}
}
}
if(AddedTriangles.empty())return Stats;
// Retirer les faces de sol MC proches du profil exact remplace le terrain coarse
// au lieu de le superposer au nouveau maillage d'horizon.
// Remove coarse upward MC faces around the exact profile instead of overlaying them.
const double VoxelSizeCm=static_cast<double>(Owner.CoreVoxelSizeMicrons)/10000.0;
const int32 OldSurfaceTriangleCount=OldCeilingStart/3;
std::vector<uint8> RemoveOldGround(static_cast<std::size_t>(OldSurfaceTriangleCount),0);
const TArray<uint8>& ExistingMasks=Mesh.TriangleSkirtFaceMasks;
const auto InterpolateProfile=[](const std::vector<double>& Values,const FGroundProfile& Profile,
double LocalX,double LocalY)
{
const double GridX=std::clamp(LocalX/Profile.GridStep,0.0,static_cast<double>(Profile.Cells));
const double GridY=std::clamp(LocalY/Profile.GridStep,0.0,static_cast<double>(Profile.Cells));
const int32 X=std::min(Profile.Cells-1,static_cast<int32>(std::floor(GridX)));
const int32 Y=std::min(Profile.Cells-1,static_cast<int32>(std::floor(GridY)));
const double TX=GridX-X,TY=GridY-Y;
const std::size_t Width=static_cast<std::size_t>(Profile.Cells+1);
const auto At=[&](int32 PX,int32 PY){return Values[static_cast<std::size_t>(PY)*Width+PX];};
if(TX>=TY)
return At(X,Y)*(1.0-TX)+At(X+1,Y)*(TX-TY)+At(X+1,Y+1)*TY;
return At(X,Y)*(1.0-TY)+At(X+1,Y+1)*TX+At(X,Y+1)*(TY-TX);
};
for(int32 Triangle=0;Triangle<OldSurfaceTriangleCount;++Triangle)
{
if(Triangle<ExistingMasks.Num()&&ExistingMasks[Triangle]!=0)continue;
const int32 IA=Mesh.Triangles[Triangle*3],IB=Mesh.Triangles[Triangle*3+1],IC=Mesh.Triangles[Triangle*3+2];
if(IA<0||IB<0||IC<0||IA>=OldSurfaceVertices||IB>=OldSurfaceVertices||IC>=OldSurfaceVertices)continue;
const FVector& A=Mesh.Vertices[IA];const FVector& B=Mesh.Vertices[IB];const FVector& C=Mesh.Vertices[IC];
double NormalZ=0.0;
if(Mesh.Normals.Num()==Mesh.Vertices.Num())
NormalZ=(Mesh.Normals[IA].Z+Mesh.Normals[IB].Z+Mesh.Normals[IC].Z)/3.0;
else
NormalZ=FVector::CrossProduct(B-A,C-A).GetSafeNormal().Z;
if(NormalZ<0.35)continue;
const double X=((A.X+B.X+C.X)/3.0)/VoxelSizeCm;
const double Y=((A.Y+B.Y+C.Y)/3.0)/VoxelSizeCm;
const double Z=((A.Z+B.Z+C.Z)/3.0)/VoxelSizeCm;
const double LocalX=X-TileOrigin.X,LocalY=Y-TileOrigin.Y;
if(LocalX<0.0||LocalY<0.0||LocalX>TileExtent||LocalY>TileExtent)continue;
for(const auto& Profile:GroundProfiles)
{
if(LocalX>Profile.Cells*Profile.GridStep||LocalY>Profile.Cells*Profile.GridStep)continue;
const double Ground=InterpolateProfile(Profile.Ground,Profile,LocalX,LocalY);
const double Cap=InterpolateProfile(Profile.Cap,Profile,LocalX,LocalY);
if(Cap-Ground<=1.0||Ground<TileMinZ/Scale||Ground>TileMaxZ/Scale
||Ground<Profile.MinimumZ||Ground>Profile.MaximumZ)continue;
const double ReplacementBand=std::max(32.0,2.0*Profile.GridStep);
if(Z>=Ground-ReplacementBand)
{
RemoveOldGround[static_cast<std::size_t>(Triangle)]=1;
++Stats.RemovedTriangles;
}
break;
}
}
const int32 AddedVertexCount=static_cast<int32>(AddedVertices.size());
auto InsertSurfaceVertices=[&](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);
};
InsertSurfaceVertices(Mesh.Vertices,AddedVertices);
InsertSurfaceVertices(Mesh.Normals,AddedNormals);
if(bHasUVs)InsertSurfaceVertices(Mesh.UVs,AddedUVs);
if(bHasColors)InsertSurfaceVertices(Mesh.Colors,AddedColors);
for(int32& Index:Mesh.Triangles)if(Index>=OldSurfaceVertices)Index+=AddedVertexCount;
const int32 OldTriangleCount=Mesh.Triangles.Num()/3;
TArray<uint8> OldMasks=Mesh.TriangleSkirtFaceMasks;OldMasks.SetNum(OldTriangleCount);
TArray<int32> CombinedTriangles;CombinedTriangles.Reserve(
Mesh.Triangles.Num()+static_cast<int32>(AddedTriangles.size()));
TArray<uint8> CombinedMasks;CombinedMasks.Reserve(
OldTriangleCount+static_cast<int32>(AddedTriangles.size()/3));
for(int32 Triangle=0;Triangle<OldCeilingStart/3;++Triangle)
{
if(RemoveOldGround[static_cast<std::size_t>(Triangle)])continue;
for(int32 Corner=0;Corner<3;++Corner)CombinedTriangles.Add(Mesh.Triangles[Triangle*3+Corner]);
CombinedMasks.Add(OldMasks[Triangle]);
}
for(const int32 Index:AddedTriangles)CombinedTriangles.Add(OldSurfaceVertices+Index);
for(std::size_t I=0;I<AddedTriangles.size()/3;++I)CombinedMasks.Add(0);
for(int32 Triangle=OldCeilingStart/3;Triangle<OldTriangleCount;++Triangle)
{
for(int32 Corner=0;Corner<3;++Corner)CombinedTriangles.Add(Mesh.Triangles[Triangle*3+Corner]);
CombinedMasks.Add(OldMasks[Triangle]);
}
Mesh.Triangles=std::move(CombinedTriangles);
Mesh.TriangleSkirtFaceMasks=std::move(CombinedMasks);
Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertexCount;
Stats.Refined=true;
return Stats;
}
FNativeCapRefinementStats RefineFarNativeCap(const FStandaloneOwnerWorld& Owner,
const WorldAssemblyManifest& Manifest,const FVoxelTileKey& Key,FVoxelMeshData& Mesh)
{
FNativeCapRefinementStats Stats;
if(Key.Level<5||!Owner.Generator)return Stats;
constexpr int32 GridStepVoxels=128;
constexpr int64 Scale=WorldAssemblySubvoxelsPerVoxel;
constexpr int64 GridStepSubvoxels=GridStepVoxels*Scale;
@@ -916,6 +1201,8 @@ namespace VoxelForgeCore
&&options.mesherRecipe!=MesherRecipe::worldAssemblerHybridV1
&&options.mesherRecipe!=MesherRecipe::worldAssemblerHybridV2)
{error="tile mesher recipe version is unsupported";return std::nullopt;}
if(level>=5&&options.farHorizonVersion==0)
{error="far-horizon recipe version must be nonzero";return std::nullopt;}
TArray<FVoxelHybridSharpFeatureBox> sharpFeatures;
if(options.mesherRecipe!=MesherRecipe::marchingCubesV1&&level==0&&impl_->worldAssembly)
{
@@ -1001,6 +1288,42 @@ namespace VoxelForgeCore
const bool bHasWorldAssembly=impl_->worldAssembly!=nullptr;
const bool bNeedsFarShell=bHasWorldAssembly&&key.lod>=7
&&(tileShape.HasFarWorld()||impl_->shape->HasFarWorld());
bool bOverlapsPlayerEdit=false;
if(tileShape.HasEditTransaction())
{
const FIntVector EditMinimum=tileShape.GetEditBoundsMin();
const FIntVector EditMaximum=tileShape.GetEditBoundsMax();
bOverlapsPlayerEdit=EditMinimum.X<=origin.X+extent+step
&&EditMaximum.X>=origin.X-step
&&EditMinimum.Y<=origin.Y+extent+step
&&EditMaximum.Y>=origin.Y-step
&&EditMinimum.Z<=origin.Z+extent+step
&&EditMaximum.Z>=origin.Z-step;
}
bool bHasNativeSurfaceHorizonPlan=false;
bool bHasIslandPlanInTile=false;
if(impl_->worldAssembly)
{
const int64 TileMinimumZ=static_cast<int64>(origin.Z);
const int64 TileMaximumZ=static_cast<int64>(origin.Z)+extent;
for(const auto& Plan:impl_->worldAssembly->plan.strates)
{
const int64 PlanMinimumZ=static_cast<int64>(std::min(Plan.bottomChunkZ,Plan.topChunkZ))*CHUNK_SIZE;
const int64 PlanMaximumZ=(static_cast<int64>(std::max(Plan.bottomChunkZ,Plan.topChunkZ))+1)*CHUNK_SIZE;
bHasNativeSurfaceHorizonPlan=bHasNativeSurfaceHorizonPlan
||(TileMinimumZ<=PlanMaximumZ&&TileMaximumZ>=PlanMinimumZ);
}
for(const auto& Entry:impl_->worldAssembly->floatingIslands)
{
const int64 PlanMinimumZ=static_cast<int64>(std::min(Entry.bottomChunkZ,Entry.topChunkZ))*CHUNK_SIZE;
const int64 PlanMaximumZ=(static_cast<int64>(std::max(Entry.bottomChunkZ,Entry.topChunkZ))+1)*CHUNK_SIZE;
bHasIslandPlanInTile=bHasIslandPlanInTile
||(TileMinimumZ<=PlanMaximumZ&&TileMaximumZ>=PlanMinimumZ);
}
}
const bool bUseNativeSurfaceHorizon=key.lod>=5&&!bOverlapsPlayerEdit
&&bHasNativeSurfaceHorizonPlan&&!bHasIslandPlanInTile;
const bool bSkipFarShellRepair=key.lod>=7&&bUseNativeSurfaceHorizon;
FStandaloneShapeWorld::ResetThreadClassificationStats();
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats();
bool bNativeSurfaceTile=false;
@@ -1082,18 +1405,6 @@ namespace VoxelForgeCore
static_cast<std::int64_t>(origin.X+extent+step)*SubvoxelScale,
static_cast<std::int64_t>(origin.Y+extent+step)*SubvoxelScale,
static_cast<std::int64_t>(origin.Z+extent+step)*SubvoxelScale};
bool bOverlapsPlayerEdit=false;
if(tileShape.HasEditTransaction())
{
const FIntVector EditMinimum=tileShape.GetEditBoundsMin();
const FIntVector EditMaximum=tileShape.GetEditBoundsMax();
bOverlapsPlayerEdit=EditMinimum.X<=origin.X+extent+step
&&EditMaximum.X>=origin.X-step
&&EditMinimum.Y<=origin.Y+extent+step
&&EditMaximum.Y>=origin.Y-step
&&EditMinimum.Z<=origin.Z+extent+step
&&EditMaximum.Z>=origin.Z-step;
}
if(!bOverlapsPlayerEdit)
{
const VoxelForgeCore::Heightfield2D Heightfield(
@@ -1190,21 +1501,52 @@ namespace VoxelForgeCore
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-MeshingStart).count();
out.diagnostics.farHorizonVersion=key.lod>=5?options.farHorizonVersion:0;
const auto FarShellStart=std::chrono::steady_clock::now();
const FStandaloneShapeWorld& farShape=tileShape.HasFarWorld()?tileShape:*impl_->shape;
if(farShape.HasFarWorld())
out.diagnostics.farShellRepairSkipped=bSkipFarShellRepair;
if(farShape.HasFarWorld()&&!bSkipFarShellRepair)
{
const auto BeforeStratum=FStandaloneShapeWorld::GetThreadClassificationStats();
const int32 BeforeStratumTriangles=mesh.Triangles.Num()/3;
const auto StratumStart=std::chrono::steady_clock::now();
AppendFarStratumShells(world,farShape,nativeKey,step,mesh);
const auto AfterStratum=FStandaloneShapeWorld::GetThreadClassificationStats();
out.diagnostics.farStratumShellMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-StratumStart).count();
out.diagnostics.farStratumFieldCalls=
AfterStratum.NativeFloatFieldCalls-BeforeStratum.NativeFloatFieldCalls;
out.diagnostics.farShellTrianglesAdded=static_cast<std::uint64_t>(
std::max(0,mesh.Triangles.Num()/3-BeforeStratumTriangles));
const auto BeforeConnector=AfterStratum;
const int32 BeforeConnectorTriangles=mesh.Triangles.Num()/3;
const auto ConnectorStart=std::chrono::steady_clock::now();
AppendFarConnectorShells(farShape,nativeKey,step,world.CoarseTileCells,mesh);
const auto AfterConnector=FStandaloneShapeWorld::GetThreadClassificationStats();
out.diagnostics.farConnectorShellMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-ConnectorStart).count();
out.diagnostics.farConnectorFieldCalls=
AfterConnector.NativeFloatFieldCalls-BeforeConnector.NativeFloatFieldCalls;
out.diagnostics.farShellTrianglesAdded+=static_cast<std::uint64_t>(
std::max(0,mesh.Triangles.Num()/3-BeforeConnectorTriangles));
}
out.diagnostics.farShellMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-FarShellStart).count();
if(impl_->worldAssembly&&key.lod>=8)
if(impl_->worldAssembly&&key.lod>=5&&!bOverlapsPlayerEdit)
{
const auto CapStats=RefineLod8NativeCap(world,*impl_->worldAssembly,nativeKey,mesh);
const auto HorizonStart=std::chrono::steady_clock::now();
const auto GroundStats=RefineFarNativeGround(
world,*impl_->worldAssembly,nativeKey,mesh);
const auto CapStats=RefineFarNativeCap(world,*impl_->worldAssembly,nativeKey,mesh);
out.diagnostics.farHorizonMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-HorizonStart).count();
out.diagnostics.capRefined=CapStats.Refined;
out.diagnostics.capRefinedCells=CapStats.Cells;
out.diagnostics.capRefinedTriangles=CapStats.Triangles;
out.diagnostics.horizonGroundTriangles=GroundStats.Triangles;
out.diagnostics.horizonGroundTrianglesRemoved=GroundStats.RemovedTriangles;
out.diagnostics.horizonCapTriangles=CapStats.Triangles;
}
out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount));
@@ -1270,6 +1612,12 @@ namespace VoxelForgeCore
{ densityHash^=std::bit_cast<std::uint32_t>(value);densityHash*=1099511628211ull; }
out.densityHash=densityHash;out.meshHash=HashViewerMesh(mesh);
out.payloadHash=HashPayload(mesh);out.topologyHash=HashTopology(mesh);
if(key.lod>=5)
{
out.meshHash=SaltFarHorizonHash(out.meshHash,options.farHorizonVersion);
out.payloadHash=SaltFarHorizonHash(out.payloadHash,options.farHorizonVersion);
out.topologyHash=SaltFarHorizonHash(out.topologyHash,options.farHorizonVersion);
}
out.hasDensityHash=out.hasMeshHash=out.hasPayloadHash=out.hasTopologyHash=true;
}
return out;