wip(assembler): M1 round 6 -- owner walk fixes: 25 cm geometry scale, one height datum, carve/fill sign, faster relief meshing

Owner saw content floating above native terrain, spiky triangles, reversed carve/fill and late relief.
Causes: shared mesher placed Core tiles on a 12.5 cm grid; content/atmosphere asked strate ranges in
12.5 cm voxels; Core edits add density where the legacy diff subtracts. Horizon LOD8 bridge edge
5.657 -> 2.265 cells. Relief mesh p95 60.8 -> 42.0 ms (LOD0). New BuildHost checks for scale, height,
blueprint carve/fill and per-kind streaming lag.

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-09 20:39:24 +02:00
1 parent f67b519e75
commit 61633cb492
18 files changed
+1708 -142

No files matched your search

+44 -31
View File
@@ -976,47 +976,60 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
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};
std::array<double,4> CornerZ{};
std::array<FVector,4> CornerNormals{};
// Les correctifs lointains suivent chaque cellule de la grille, sans grande nappe.
// Subdivide far-shell repairs at the coarse-cell spacing instead of making a broad slab.
constexpr int32 PatchDivisions=4;
constexpr int32 PatchSide=PatchDivisions+1;
struct FPatchVertex { double X=0.0,Y=0.0,Z=0.0;FVector Normal=FVector(0,0,1); };
std::array<FPatchVertex,PatchSide*PatchSide> Patch{};
bool bValidPatch=true;
for(int32 Corner=0;Corner<4;++Corner)
for(int32 PY=0;PY<PatchSide&&bValidPatch;++PY)
for(int32 PX=0;PX<PatchSide;++PX)
{
const double PredictedZ=SurfaceZ(Xs[Corner],Ys[Corner]);
const std::vector<double> CornerCrossings=FindFarStratumCrossings(
Shape,Xs[Corner],Ys[Corner],ZMin,ZMax);
if(CornerCrossings.empty()){bValidPatch=false;break;}
CornerZ[Corner]=*std::min_element(CornerCrossings.begin(),CornerCrossings.end(),
FPatchVertex& Vertex=Patch[static_cast<std::size_t>(PY*PatchSide+PX)];
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);});
const float CornerGX=Density(Xs[Corner]+2,Ys[Corner],CornerZ[Corner])
-Density(Xs[Corner]-2,Ys[Corner],CornerZ[Corner]);
const float CornerGY=Density(Xs[Corner],Ys[Corner]+2,CornerZ[Corner])
-Density(Xs[Corner],Ys[Corner]-2,CornerZ[Corner]);
const float CornerGZ=Density(Xs[Corner],Ys[Corner],CornerZ[Corner]+2)
-Density(Xs[Corner],Ys[Corner],CornerZ[Corner]-2);
CornerNormals[Corner]=FVector(CornerGX,CornerGY,CornerGZ);
if(CornerNormals[Corner].SizeSquared()<1.0e-8)
CornerNormals[Corner]=Normal;
else CornerNormals[Corner].Normalize();
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)
-Density(Vertex.X,Vertex.Y-2,Vertex.Z);
const float VertexGZ=Density(Vertex.X,Vertex.Y,Vertex.Z+2)
-Density(Vertex.X,Vertex.Y,Vertex.Z-2);
Vertex.Normal=FVector(VertexGX,VertexGY,VertexGZ);
if(Vertex.Normal.SizeSquared()<1.0e-8)Vertex.Normal=Normal;
else Vertex.Normal.Normalize();
}
// A center crossing alone cannot justify a large tangent quad: it can
// bridge air over an isolated far-LOD feature and draw as a horizon slab.
// Keep the fallback only when the source field has a matching crossing
// at every corner, and place each corner on that source crossing.
// Une seule traversee centrale ne justifie pas une grande nappe de terrain.
// A center crossing alone cannot justify a broad patch that bridges horizon relief.
// Require a source-field crossing at every coarse-cell sample before emitting it.
if(!bValidPatch)continue;
for(int32 Corner=0;Corner<4;++Corner)
const int32 Base=static_cast<int32>(NewVertices.size());
for(const FPatchVertex& Vertex:Patch)
{
NewVertices.emplace_back(Xs[Corner]*25.0,Ys[Corner]*25.0,CornerZ[Corner]*25.0);
NewNormals.push_back(CornerNormals[Corner]);
if(bHasUVs)NewUVs.emplace_back(static_cast<float>((Xs[Corner]-Origin.X)/256.0),
static_cast<float>((Ys[Corner]-Origin.Y)/256.0));
NewVertices.emplace_back(Vertex.X*25.0,Vertex.Y*25.0,Vertex.Z*25.0);
NewNormals.push_back(Vertex.Normal);
if(bHasUVs)NewUVs.emplace_back(static_cast<float>((Vertex.X-Origin.X)/256.0),
static_cast<float>((Vertex.Y-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});
for(int32 PY=0;PY<PatchDivisions;++PY)
for(int32 PX=0;PX<PatchDivisions;++PX)
{
const int32 I0=Base+PY*PatchSide+PX;
const int32 I1=I0+1;
const int32 I2=I1+PatchSide;
const int32 I3=I0+PatchSide;
if(bBelowSolid)NewTriangles.insert(NewTriangles.end(),{I0,I1,I3,I1,I2,I3});
else NewTriangles.insert(NewTriangles.end(),{I0,I3,I1,I1,I3,I2});
}
}
}
}
+115 -7
View File
@@ -369,8 +369,8 @@ namespace VoxelForgeCore
const int32 GY=static_cast<int32>(Index/static_cast<std::size_t>(Cells+1));
const double XSub=static_cast<double>(TileOrigin.X+GX*GridStepVoxels)*Scale;
const double YSub=static_cast<double>(TileOrigin.Y+GY*GridStepVoxels)*Scale;
AddedVertices.emplace_back(XSub/Scale*VOXEL_SIZE,YSub/Scale*VOXEL_SIZE,
Sample.Cap/Scale*VOXEL_SIZE);
AddedVertices.emplace_back(XSub/Scale*25.0,YSub/Scale*25.0,
Sample.Cap/Scale*25.0);
AddedNormals.push_back(Sample.Normal);
if(bHasUVs)AddedUVs.emplace_back(static_cast<float>(XSub/Scale),
static_cast<float>(YSub/Scale));
@@ -425,8 +425,8 @@ namespace VoxelForgeCore
for(int32 I=0;I<Count;++I)
{
const auto& V=PolygonB[I];
AddedVertices.emplace_back(V.XSub/Scale*VOXEL_SIZE,
V.YSub/Scale*VOXEL_SIZE,V.ZSub/Scale*VOXEL_SIZE);
AddedVertices.emplace_back(V.XSub/Scale*25.0,
V.YSub/Scale*25.0,V.ZSub/Scale*25.0);
AddedNormals.push_back(V.Normal);
if(bHasUVs)AddedUVs.emplace_back(static_cast<float>(V.XSub/Scale),
static_cast<float>(V.YSub/Scale));
@@ -893,11 +893,13 @@ namespace VoxelForgeCore
const FStandaloneShapeWorld& tileShape=page?*page:*impl_->shape;
UVoxelMarchingCubesMesher mesher;
mesher.SetGenerator(world.Generator.get());
mesher.SetGeometryVoxelSizeCm(25.0f);
mesher.SetStandaloneShapeWorld(&tileShape);
mesher.bStandaloneSkipUvAndColors=options.skipUvAndColors;
mesher.bStandaloneSkipUV=options.skipUv;mesher.bStandaloneSkipColors=options.skipColors;
mesher.bStandaloneSkipMaterialGroups=options.skipMaterialGroups;
mesher.bGenerateSkirts=world.bGenerateSkirts;mesher.SkirtCells=static_cast<float>(world.SkirtCells);
mesher.bGenerateSkirts=world.bGenerateSkirts;
mesher.SkirtCells=static_cast<float>(world.SkirtCells)*12.5f/25.0f;
mesher.LODOctaveDrop=world.LODOctaveDrop;
const int32 level=key.lod,extent=CHUNK_SIZE<<level;
const FVoxelTileKey nativeKey=ToNative(key);
@@ -942,10 +944,16 @@ namespace VoxelForgeCore
FStandaloneShapeWorld::ResetThreadClassificationStats();
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats();
bool bNativeSurfaceTile=false;
bool bNativeSurfaceBandSafe=false;
if(bHasWorldAssembly)
{
const auto Classification=tileShape.ClassifyBoxDetailed(origin,step,cells);
bNativeSurfaceTile=Classification.bNativeSurface;
// NativeSurfaceDensityAt consumes only the bounded manifest heightfield and its
// bounded type-2/type-4 features. Unknown tile occupancy does not make that Z support
// unknown; edits and cross-strate tiles are still excluded below.
// NativeSurfaceDensityAt ne consomme que le champ et les volumes bornes du manifeste.
bNativeSurfaceBandSafe=bNativeSurfaceTile;
if(bNativeSurfaceTile)
{
out.diagnostics.tileWasClassified=true;
@@ -983,8 +991,107 @@ namespace VoxelForgeCore
}
}
const auto MeshingStart=std::chrono::steady_clock::now();
int32 MeshBandMinZVoxel=options.bandMinZVoxel;
int32 MeshBandMaxZVoxel=options.bandMaxZVoxel;
bool bNativeHeightfieldBandLimited=false;
if(bHasWorldAssembly&&bNativeSurfaceBandSafe
&&!options.hashDensity&&!options.captureDensitySamples&&world.Manager!=nullptr)
{
// La surface 2D et les bornes de volume du manifeste definissent tout le support Z.
// The 2D surface and manifest volume bounds cover every possible density crossing.
const double TileCenterZ=static_cast<double>(origin.Z)+static_cast<double>(extent)*0.5;
const int32 CenterChunkZ=FMath::FloorToInt(TileCenterZ/static_cast<double>(CHUNK_SIZE));
const int32 StrateIndex=world.Manager->GetStrateIndex(
static_cast<float>(TileCenterZ*VOXEL_SIZE));
const auto PlanIt=std::find_if(impl_->worldAssembly->plan.strates.begin(),
impl_->worldAssembly->plan.strates.end(),[StrateIndex,CenterChunkZ](const auto& Plan)
{return Plan.strateIndex==StrateIndex&&CenterChunkZ<=Plan.topChunkZ
&&CenterChunkZ>=Plan.bottomChunkZ;});
if(PlanIt!=impl_->worldAssembly->plan.strates.end())
{
constexpr std::int64_t SubvoxelScale=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
const std::int64_t TileMinimum[3]={
static_cast<std::int64_t>(origin.X-step)*SubvoxelScale,
static_cast<std::int64_t>(origin.Y-step)*SubvoxelScale,
static_cast<std::int64_t>(origin.Z-step)*SubvoxelScale};
const std::int64_t TileMaximum[3]={
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(
impl_->worldAssembly->recipe,*PlanIt);
double RelevantMinimum=std::numeric_limits<double>::infinity();
double RelevantMaximum=-std::numeric_limits<double>::infinity();
const double TileMinimumZ=static_cast<double>(origin.Z-step);
const double TileMaximumZ=static_cast<double>(origin.Z+extent+step);
for(int32 GridY=0;GridY<=cells+2;++GridY)
for(int32 GridX=0;GridX<=cells+2;++GridX)
{
const std::int64_t XSub=static_cast<std::int64_t>(origin.X-step+GridX*step)
*SubvoxelScale;
const std::int64_t YSub=static_cast<std::int64_t>(origin.Y-step+GridY*step)
*SubvoxelScale;
const auto Column=Heightfield.point(XSub,YSub,0);
const double Ground=static_cast<double>(Column.groundHeightSubvoxels)
/static_cast<double>(SubvoxelScale);
const double Cap=static_cast<double>(Column.capHeightSubvoxels)
/static_cast<double>(SubvoxelScale);
if(Ground>=TileMinimumZ&&Ground<=TileMaximumZ)
{
RelevantMinimum=std::min(RelevantMinimum,Ground);
RelevantMaximum=std::max(RelevantMaximum,Ground);
}
if(Cap>=TileMinimumZ&&Cap<=TileMaximumZ)
{
RelevantMinimum=std::min(RelevantMinimum,Cap);
RelevantMaximum=std::max(RelevantMaximum,Cap);
}
}
for(const auto& Feature:PlanIt->features)
{
if(Feature.type!=2&&Feature.type!=4)continue;
const bool bOverlapsXY=Feature.minimumSubvoxels[0]<=TileMaximum[0]
&&Feature.maximumSubvoxels[0]>=TileMinimum[0]
&&Feature.minimumSubvoxels[1]<=TileMaximum[1]
&&Feature.maximumSubvoxels[1]>=TileMinimum[1];
const bool bOverlapsTileZ=Feature.minimumSubvoxels[2]<=TileMaximum[2]
&&Feature.maximumSubvoxels[2]>=TileMinimum[2];
if(!bOverlapsXY||!bOverlapsTileZ)continue;
const double FeatureMinimum=static_cast<double>(Feature.minimumSubvoxels[2])
/static_cast<double>(SubvoxelScale);
const double FeatureMaximum=static_cast<double>(Feature.maximumSubvoxels[2])
/static_cast<double>(SubvoxelScale);
RelevantMinimum=std::min(RelevantMinimum,FeatureMinimum);
RelevantMaximum=std::max(RelevantMaximum,FeatureMaximum);
}
if(std::isfinite(RelevantMinimum)&&std::isfinite(RelevantMaximum))
{
const int32 NativeBandMinimum=FMath::FloorToInt(RelevantMinimum)-step;
const int32 NativeBandMaximum=FMath::CeilToInt(RelevantMaximum)+step;
MeshBandMinZVoxel=FMath::Max(MeshBandMinZVoxel,NativeBandMinimum);
MeshBandMaxZVoxel=FMath::Min(MeshBandMaxZVoxel,NativeBandMaximum);
bNativeHeightfieldBandLimited=static_cast<std::int64_t>(MeshBandMaxZVoxel)
-MeshBandMinZVoxel<static_cast<std::int64_t>(extent)+2*step;
}
}
}
}
FVoxelMeshData mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
options.bandMinZVoxel,options.bandMaxZVoxel,&sampleCount,nullptr,nullptr,
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-MeshingStart).count();
@@ -1027,8 +1134,9 @@ namespace VoxelForgeCore
out.diagnostics.unknownBoxes=Counts.UnknownBoxes;
out.diagnostics.tileClass=TileClass::unknown;
}
out.diagnostics.meshingMilliseconds=MeshingMilliseconds;
}
out.diagnostics.heightfieldBandLimited=bNativeHeightfieldBandLimited;
out.diagnostics.meshingMilliseconds=MeshingMilliseconds;
out.meshPayloadAllocatedBytes=static_cast<std::uint64_t>(mesh.Vertices.GetAllocatedSize())
+static_cast<std::uint64_t>(mesh.Normals.GetAllocatedSize())
+static_cast<std::uint64_t>(mesh.UVs.GetAllocatedSize())