standalone: owner review fixes -- paged streaming in Core, no LOD fins, LOD0 stair patches fixed
From the owner's fly-through ("very impressive, in some cases slightly better than the original"):
- Streaming: Core compiles world-aligned fine-detail pages for tiles entering the view and releases
them when they leave; the viewer keeps resident tiles. One chunk crossing: 144 new tiles, 1,822
kept (was a full rebuild). A tile built as a late page hashes identically (density, mesh, payload,
topology) to the same tile from an initial build.
- LOD>=1 fins: off-surface planar connector panels are now checked against the source surface and
projected onto it; coarse audit 0 off-surface panels; far survival passes, bad-shell still fails.
- LOD0 stair patches: 2,630 sign-changing edges had one endpoint at the fixed +-4.125 fallback
(carve surfaces were excluded from distance storage); distances now completed analytically, 462
packed signs corrected; the audit now fails on any sign edge missing either endpoint (0 of 952,997).
- W1/W8 hashes equal; routes 5642/5642; look, passage and far audits pass.
Verified by me, interleaved (8 workers, cold): 1.014 s vs 1.008 s for aeeedaa, no regression.
Open: a chunk crossing still costs ~1.2 s of page compilation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
aeeedaa73d
commit
3b37d5702e
6 files changed
+938
-24
No files matched your search
@@ -48,6 +48,32 @@ build.includeFarWorld = true;
|
|||||||
if (!world.compile(build, hostScheduler, error)) { /* report error */ }
|
if (!world.compile(build, hostScheduler, error)) { /* report error */ }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For a streamed shape world, compile the initial camera region once, then add deterministic detail
|
||||||
|
pages before generating any newly desired fine tile outside that region:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const auto entering = world.desiredTiles(newCameraVoxels);
|
||||||
|
VoxelForgeCore::TileGenerationOptions options;
|
||||||
|
std::size_t pageCoveredTiles = 0;
|
||||||
|
if (!world.compileTileRegion(entering, 8, hostScheduler, error, &pageCoveredTiles)) {
|
||||||
|
/* report error */
|
||||||
|
}
|
||||||
|
for (const auto& key : entering) {
|
||||||
|
if (auto mesh = world.generateTile(key, options, error)) { /* upload and retain */ }
|
||||||
|
}
|
||||||
|
world.retainTileRegions(entering);
|
||||||
|
```
|
||||||
|
|
||||||
|
The Core groups fine tiles into pages using absolute tile coordinates, so a page's contents do not
|
||||||
|
depend on the camera or the order in which the player reaches it. Pages cover a 128-voxel cube (or
|
||||||
|
one larger coarse tile), with an 8-voxel sampling apron. `compileTileRegion` skips LODs at or above
|
||||||
|
`fullResolutionLodCount`, keys covered by the initial `ShapeBuildRequest`, and pages already
|
||||||
|
resident. Its optional output counts requested fine tile keys that needed a newly compiled page;
|
||||||
|
it does not count the page's internal lattice samples. After all pages needed by the next desired
|
||||||
|
set compile, call `retainTileRegions` with that set to release pages no longer needed. The Core
|
||||||
|
owns page lifetime and mesh data; the host keeps still-visible meshes resident until replacement
|
||||||
|
tiles are generated and uploaded.
|
||||||
|
|
||||||
## Queries and tiles
|
## Queries and tiles
|
||||||
|
|
||||||
`densityAt(position)` returns signed density; negative is solid and nonnegative is air.
|
`densityAt(position)` returns signed density; negative is solid and nonnegative is air.
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ namespace VoxelForgeCore
|
|||||||
{
|
{
|
||||||
struct Vec2f { float x = 0.0f, y = 0.0f; };
|
struct Vec2f { float x = 0.0f, y = 0.0f; };
|
||||||
struct Vec3d { double x = 0.0, y = 0.0, z = 0.0; };
|
struct Vec3d { double x = 0.0, y = 0.0, z = 0.0; };
|
||||||
struct Vec3i { std::int32_t x = 0, y = 0, z = 0; };
|
struct Vec3i
|
||||||
|
{
|
||||||
|
std::int32_t x = 0, y = 0, z = 0;
|
||||||
|
friend bool operator==(const Vec3i&, const Vec3i&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
struct TileKey
|
struct TileKey
|
||||||
{
|
{
|
||||||
@@ -196,6 +200,13 @@ namespace VoxelForgeCore
|
|||||||
|
|
||||||
bool compile(const ShapeBuildRequest& request, const JobScheduler& scheduler,
|
bool compile(const ShapeBuildRequest& request, const JobScheduler& scheduler,
|
||||||
std::string& error);
|
std::string& error);
|
||||||
|
// Compile any missing absolute-coordinate detail pages needed by entering fine tiles.
|
||||||
|
// The optional count is requested tile keys newly covered by compiled pages, not lattice
|
||||||
|
// samples. Existing pages stay resident until retainTileRegions.
|
||||||
|
bool compileTileRegion(std::span<const TileKey> tiles, std::uint32_t workerLimit,
|
||||||
|
const JobScheduler& scheduler, std::string& error,
|
||||||
|
std::size_t* outCompiledTiles = nullptr);
|
||||||
|
void retainTileRegions(std::span<const TileKey> desiredTiles);
|
||||||
void buildFarWorld();
|
void buildFarWorld();
|
||||||
bool isCompiled() const noexcept;
|
bool isCompiled() const noexcept;
|
||||||
bool hasFarWorld() const noexcept;
|
bool hasFarWorld() const noexcept;
|
||||||
@@ -221,6 +232,7 @@ namespace VoxelForgeCore
|
|||||||
std::vector<std::uint32_t> farConnectorCandidates(Vec3i minVoxel, Vec3i maxVoxel) const;
|
std::vector<std::uint32_t> farConnectorCandidates(Vec3i minVoxel, Vec3i maxVoxel) const;
|
||||||
|
|
||||||
bool auditDetailPassing() const;
|
bool auditDetailPassing() const;
|
||||||
|
bool auditGridStairIntegrity() const;
|
||||||
bool auditSurfaceFamilies(bool passageOnly = false) const;
|
bool auditSurfaceFamilies(bool passageOnly = false) const;
|
||||||
bool auditViewSurface(Vec3d cameraVoxel, double yawDegrees, double pitchDegrees) const;
|
bool auditViewSurface(Vec3d cameraVoxel, double yawDegrees, double pitchDegrees) const;
|
||||||
bool debugCriticalAirAt(Vec3d voxelPosition) const;
|
bool debugCriticalAirAt(Vec3d voxelPosition) const;
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
|
|||||||
std::vector<FVector2f> NewUVs;
|
std::vector<FVector2f> NewUVs;
|
||||||
std::vector<FColor> NewColors;
|
std::vector<FColor> NewColors;
|
||||||
std::vector<int32> NewTriangles;
|
std::vector<int32> NewTriangles;
|
||||||
|
std::vector<std::array<int32,4>> NewQuads;
|
||||||
const int32 OldVertexCount=Mesh.Vertices.Num();
|
const int32 OldVertexCount=Mesh.Vertices.Num();
|
||||||
const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,OldVertexCount);
|
const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,OldVertexCount);
|
||||||
const bool bHasUVs=Mesh.UVs.Num()==OldVertexCount;
|
const bool bHasUVs=Mesh.UVs.Num()==OldVertexCount;
|
||||||
@@ -579,7 +580,7 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
|
|||||||
const int32 C=RingVertexIndex[Ring+1][FaceSide];
|
const int32 C=RingVertexIndex[Ring+1][FaceSide];
|
||||||
const int32 D=RingVertexIndex[Ring+1][Next];
|
const int32 D=RingVertexIndex[Ring+1][Next];
|
||||||
// Shared panels are emitted once even when both adjoining radial rays are missing.
|
// Shared panels are emitted once even when both adjoining radial rays are missing.
|
||||||
NewTriangles.insert(NewTriangles.end(),{A,C,B,B,C,D});
|
NewQuads.push_back({A,B,C,D});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -587,7 +588,191 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
|
|||||||
RunStart=RunEnd+1;
|
RunStart=RunEnd+1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(NewVertices.empty()||NewTriangles.empty())return;
|
if(NewVertices.empty()||NewQuads.empty())return;
|
||||||
|
|
||||||
|
// The survival repair normally emits long connected strips. A few isolated
|
||||||
|
// two-triangle panels can be assembled from a radius borrowed at another axial
|
||||||
|
// sample, though, leaving a free-edged fin away from the sampled density surface.
|
||||||
|
// Match the visible coarse-panel audit's topology and six density samples here.
|
||||||
|
std::vector<uint8> QuadUseCount(NewVertices.size(),0);
|
||||||
|
for(const auto& Quad:NewQuads)
|
||||||
|
for(const int32 Vertex:Quad)
|
||||||
|
if(Vertex>=0&&static_cast<size_t>(Vertex)<QuadUseCount.size()
|
||||||
|
&&QuadUseCount[static_cast<size_t>(Vertex)]<255)
|
||||||
|
++QuadUseCount[static_cast<size_t>(Vertex)];
|
||||||
|
std::vector<uint8> KeepQuad(NewQuads.size(),1);
|
||||||
|
std::vector<std::array<int32,4>> RefinedQuads;
|
||||||
|
const double ResidualLimit=std::max(2.0,Step*0.10);
|
||||||
|
const double TileMin[3]={static_cast<double>(TileOrigin.X),static_cast<double>(TileOrigin.Y),
|
||||||
|
static_cast<double>(TileOrigin.Z)};
|
||||||
|
const double TileMax[3]={TileMin[0]+TileExtent,TileMin[1]+TileExtent,TileMin[2]+TileExtent};
|
||||||
|
const auto ToVoxels=[&](int32 Vertex)
|
||||||
|
{
|
||||||
|
return FVector(NewVertices[Vertex].X*0.04,NewVertices[Vertex].Y*0.04,
|
||||||
|
NewVertices[Vertex].Z*0.04);
|
||||||
|
};
|
||||||
|
const auto IsTileBoundaryEdge=[&](const FVector& A,const FVector& B)
|
||||||
|
{
|
||||||
|
for(int32 AxisIndex=0;AxisIndex<3;++AxisIndex)
|
||||||
|
{
|
||||||
|
const double PA=AxisIndex==0?A.X:(AxisIndex==1?A.Y:A.Z);
|
||||||
|
const double PB=AxisIndex==0?B.X:(AxisIndex==1?B.Y:B.Z);
|
||||||
|
if((std::abs(PA-TileMin[AxisIndex])<0.75&&std::abs(PB-TileMin[AxisIndex])<0.75)
|
||||||
|
||(std::abs(PA-TileMax[AxisIndex])<0.75&&std::abs(PB-TileMax[AxisIndex])<0.75))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
for(size_t QuadIndex=0;QuadIndex<NewQuads.size();++QuadIndex)
|
||||||
|
{
|
||||||
|
const auto& Quad=NewQuads[QuadIndex];
|
||||||
|
if(std::any_of(Quad.begin(),Quad.end(),[&](int32 Vertex)
|
||||||
|
{return Vertex<0||static_cast<size_t>(Vertex)>=QuadUseCount.size()
|
||||||
|
||QuadUseCount[static_cast<size_t>(Vertex)]!=1;}))continue;
|
||||||
|
const FVector A=ToVoxels(Quad[0]),B=ToVoxels(Quad[1]);
|
||||||
|
const FVector C=ToVoxels(Quad[2]),D=ToVoxels(Quad[3]);
|
||||||
|
const FVector N0=FVector::CrossProduct(C-A,B-A);
|
||||||
|
const FVector N1=FVector::CrossProduct(C-B,D-B);
|
||||||
|
const double Area0=N0.Size(),Area1=N1.Size();
|
||||||
|
const double AreaSum=Area0+Area1;
|
||||||
|
if(AreaSum<=1.0e-12)continue;
|
||||||
|
const double Coherence=(N0+N1).Size()/AreaSum;
|
||||||
|
if(Coherence<0.995)continue;
|
||||||
|
if(IsTileBoundaryEdge(A,B)&&IsTileBoundaryEdge(B,D)
|
||||||
|
&&IsTileBoundaryEdge(D,C)&&IsTileBoundaryEdge(C,A))continue;
|
||||||
|
const FVector Samples[6]={A,B,C,D,(A+C+B)/3.0,(B+C+D)/3.0};
|
||||||
|
int32 OffSourceSamples=0;
|
||||||
|
for(const FVector& P:Samples)
|
||||||
|
if(std::abs(Shape.DensityAt(static_cast<float>(P.X),static_cast<float>(P.Y),
|
||||||
|
static_cast<float>(P.Z)))>ResidualLimit)++OffSourceSamples;
|
||||||
|
if(OffSourceSamples*4<6)continue;
|
||||||
|
|
||||||
|
constexpr int32 Subdivisions=8;
|
||||||
|
const FVector PreferredNormal=(NewNormals[Quad[0]]+NewNormals[Quad[1]]
|
||||||
|
+NewNormals[Quad[2]]+NewNormals[Quad[3]]).GetSafeNormal();
|
||||||
|
const auto SourceGradient=[&](const FVector& P)
|
||||||
|
{
|
||||||
|
constexpr float H=2.0f;
|
||||||
|
const float GX=(Shape.DensityAt(static_cast<float>(P.X+H),static_cast<float>(P.Y),
|
||||||
|
static_cast<float>(P.Z))-Shape.DensityAt(static_cast<float>(P.X-H),
|
||||||
|
static_cast<float>(P.Y),static_cast<float>(P.Z)))*0.25f;
|
||||||
|
const float GY=(Shape.DensityAt(static_cast<float>(P.X),static_cast<float>(P.Y+H),
|
||||||
|
static_cast<float>(P.Z))-Shape.DensityAt(static_cast<float>(P.X),
|
||||||
|
static_cast<float>(P.Y-H),static_cast<float>(P.Z)))*0.25f;
|
||||||
|
const float GZ=(Shape.DensityAt(static_cast<float>(P.X),static_cast<float>(P.Y),
|
||||||
|
static_cast<float>(P.Z+H))-Shape.DensityAt(static_cast<float>(P.X),
|
||||||
|
static_cast<float>(P.Y),static_cast<float>(P.Z-H)))*0.25f;
|
||||||
|
return FVector(GX,GY,GZ);
|
||||||
|
};
|
||||||
|
std::vector<FVector> PatchVertices;
|
||||||
|
std::vector<FVector> PatchNormals;
|
||||||
|
std::vector<FVector2f> PatchUVs;
|
||||||
|
std::vector<FColor> PatchColors;
|
||||||
|
const auto ProjectToSurface=[&](FVector& P,FVector& Normal)
|
||||||
|
{
|
||||||
|
for(int32 Iteration=0;Iteration<12;++Iteration)
|
||||||
|
{
|
||||||
|
const double Density=Shape.DensityAt(static_cast<float>(P.X),
|
||||||
|
static_cast<float>(P.Y),static_cast<float>(P.Z));
|
||||||
|
if(std::abs(Density)<=0.25)break;
|
||||||
|
FVector Gradient=SourceGradient(P);
|
||||||
|
const double GradientSquared=Gradient.SizeSquared();
|
||||||
|
if(GradientSquared<1.0e-8)return false;
|
||||||
|
double Distance=std::clamp(Density/GradientSquared,-64.0,64.0);
|
||||||
|
P-=Gradient*Distance;
|
||||||
|
}
|
||||||
|
if(std::abs(Shape.DensityAt(static_cast<float>(P.X),static_cast<float>(P.Y),
|
||||||
|
static_cast<float>(P.Z)))>2.0)return false;
|
||||||
|
Normal=SourceGradient(P);
|
||||||
|
if(Normal.SizeSquared()<1.0e-8)return false;
|
||||||
|
Normal.Normalize();
|
||||||
|
if(PreferredNormal.SizeSquared()>1.0e-8&&FVector::DotProduct(Normal,PreferredNormal)<0.0)
|
||||||
|
Normal*= -1.0;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
std::vector<int32> Grid(static_cast<size_t>(Subdivisions+1)*(Subdivisions+1),INDEX_NONE);
|
||||||
|
bool bProjectionSucceeded=true;
|
||||||
|
for(int32 Y=0;Y<=Subdivisions&&bProjectionSucceeded;++Y)
|
||||||
|
{
|
||||||
|
const double V=static_cast<double>(Y)/Subdivisions;
|
||||||
|
for(int32 X=0;X<=Subdivisions;++X)
|
||||||
|
{
|
||||||
|
const double U=static_cast<double>(X)/Subdivisions;
|
||||||
|
FVector P=A*((1.0-U)*(1.0-V))+B*(U*(1.0-V))+C*((1.0-U)*V)+D*(U*V);
|
||||||
|
FVector Normal;
|
||||||
|
if(!ProjectToSurface(P,Normal)){bProjectionSucceeded=false;break;}
|
||||||
|
const int32 LocalIndex=static_cast<int32>(PatchVertices.size());
|
||||||
|
Grid[static_cast<size_t>(Y)*(Subdivisions+1)+X]=LocalIndex;
|
||||||
|
PatchVertices.emplace_back(P.X*25.0,P.Y*25.0,P.Z*25.0);
|
||||||
|
PatchNormals.push_back(Normal);
|
||||||
|
if(bHasUVs)
|
||||||
|
{
|
||||||
|
const FVector2f UA=NewUVs[Quad[0]],UB=NewUVs[Quad[1]];
|
||||||
|
const FVector2f UC=NewUVs[Quad[2]],UD=NewUVs[Quad[3]];
|
||||||
|
PatchUVs.emplace_back(static_cast<float>(UA.X*((1-U)*(1-V))+UB.X*(U*(1-V))
|
||||||
|
+UC.X*((1-U)*V)+UD.X*(U*V)),
|
||||||
|
static_cast<float>(UA.Y*((1-U)*(1-V))+UB.Y*(U*(1-V))
|
||||||
|
+UC.Y*((1-U)*V)+UD.Y*(U*V)));
|
||||||
|
}
|
||||||
|
if(bHasColors)PatchColors.push_back(NewColors[Quad[0]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!bProjectionSucceeded)continue;
|
||||||
|
|
||||||
|
KeepQuad[QuadIndex]=0;
|
||||||
|
const int32 PatchBase=static_cast<int32>(NewVertices.size());
|
||||||
|
NewVertices.insert(NewVertices.end(),PatchVertices.begin(),PatchVertices.end());
|
||||||
|
NewNormals.insert(NewNormals.end(),PatchNormals.begin(),PatchNormals.end());
|
||||||
|
if(bHasUVs)NewUVs.insert(NewUVs.end(),PatchUVs.begin(),PatchUVs.end());
|
||||||
|
if(bHasColors)NewColors.insert(NewColors.end(),PatchColors.begin(),PatchColors.end());
|
||||||
|
for(int32 Y=0;Y<Subdivisions;++Y)
|
||||||
|
for(int32 X=0;X<Subdivisions;++X)
|
||||||
|
{
|
||||||
|
const int32 A0=PatchBase+Grid[static_cast<size_t>(Y)*(Subdivisions+1)+X];
|
||||||
|
const int32 B0=PatchBase+Grid[static_cast<size_t>(Y)*(Subdivisions+1)+X+1];
|
||||||
|
const int32 C0=PatchBase+Grid[static_cast<size_t>(Y+1)*(Subdivisions+1)+X];
|
||||||
|
const int32 D0=PatchBase+Grid[static_cast<size_t>(Y+1)*(Subdivisions+1)+X+1];
|
||||||
|
RefinedQuads.push_back({A0,B0,C0,D0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::array<int32,4>> FinalQuads;
|
||||||
|
const size_t KeptQuadCount=static_cast<size_t>(std::count(KeepQuad.begin(),KeepQuad.end(),uint8{1}));
|
||||||
|
FinalQuads.reserve(KeptQuadCount+RefinedQuads.size());
|
||||||
|
for(size_t QuadIndex=0;QuadIndex<NewQuads.size();++QuadIndex)
|
||||||
|
if(KeepQuad[QuadIndex])FinalQuads.push_back(NewQuads[QuadIndex]);
|
||||||
|
FinalQuads.insert(FinalQuads.end(),RefinedQuads.begin(),RefinedQuads.end());
|
||||||
|
if(FinalQuads.empty())return;
|
||||||
|
std::vector<int32> Remap(NewVertices.size(),INDEX_NONE);
|
||||||
|
std::vector<FVector> KeptVertices,KeptNormals;
|
||||||
|
std::vector<FVector2f> KeptUVs;
|
||||||
|
std::vector<FColor> KeptColors;
|
||||||
|
KeptVertices.reserve(FinalQuads.size()*2);KeptNormals.reserve(FinalQuads.size()*2);
|
||||||
|
if(bHasUVs)KeptUVs.reserve(FinalQuads.size()*2);
|
||||||
|
if(bHasColors)KeptColors.reserve(FinalQuads.size()*2);
|
||||||
|
const auto RemapVertex=[&](int32 Vertex)
|
||||||
|
{
|
||||||
|
int32& Mapped=Remap[static_cast<size_t>(Vertex)];
|
||||||
|
if(Mapped==INDEX_NONE)
|
||||||
|
{
|
||||||
|
Mapped=static_cast<int32>(KeptVertices.size());
|
||||||
|
KeptVertices.push_back(NewVertices[static_cast<size_t>(Vertex)]);
|
||||||
|
KeptNormals.push_back(NewNormals[static_cast<size_t>(Vertex)]);
|
||||||
|
if(bHasUVs)KeptUVs.push_back(NewUVs[static_cast<size_t>(Vertex)]);
|
||||||
|
if(bHasColors)KeptColors.push_back(NewColors[static_cast<size_t>(Vertex)]);
|
||||||
|
}
|
||||||
|
return Mapped;
|
||||||
|
};
|
||||||
|
NewTriangles.reserve(KeptQuadCount*6);
|
||||||
|
for(const auto& Quad:FinalQuads)
|
||||||
|
{
|
||||||
|
const int32 A=RemapVertex(Quad[0]),B=RemapVertex(Quad[1]);
|
||||||
|
const int32 C=RemapVertex(Quad[2]),D=RemapVertex(Quad[3]);
|
||||||
|
NewTriangles.insert(NewTriangles.end(),{A,C,B,B,C,D});
|
||||||
|
}
|
||||||
|
NewVertices=std::move(KeptVertices);NewNormals=std::move(KeptNormals);
|
||||||
|
if(bHasUVs)NewUVs=std::move(KeptUVs);
|
||||||
|
if(bHasColors)NewColors=std::move(KeptColors);
|
||||||
|
|
||||||
const int32 AddedVertices=static_cast<int32>(NewVertices.size());
|
const int32 AddedVertices=static_cast<int32>(NewVertices.size());
|
||||||
auto InsertVertices=[&](auto& Existing,const auto& Added)
|
auto InsertVertices=[&](auto& Existing,const auto& Added)
|
||||||
|
|||||||
@@ -319,6 +319,7 @@ public:
|
|||||||
|
|
||||||
Stage=Clock::now();
|
Stage=Clock::now();
|
||||||
ApplyDetailBand(Stats.DetailBandLines);
|
ApplyDetailBand(Stats.DetailBandLines);
|
||||||
|
Stats.SurfaceProxyNodes+=CompleteSignEdgeDistances();
|
||||||
Stats.SurfaceSeconds=std::chrono::duration<double>(Clock::now()-Stage).count();
|
Stats.SurfaceSeconds=std::chrono::duration<double>(Clock::now()-Stage).count();
|
||||||
ValidatePassageRoutes();
|
ValidatePassageRoutes();
|
||||||
Built=true;
|
Built=true;
|
||||||
@@ -782,6 +783,89 @@ public:
|
|||||||
return bPassageOnly?bPassageDetail:(Tested>0&&FlatShare<20.0&&bPass&&bPassageDetail);
|
return bPassageOnly?bPassageDetail:(Tested>0&&FlatShare<20.0&&bPass&&bPassageDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AuditGridStairIntegrity() const
|
||||||
|
{
|
||||||
|
uint64 CheckedEdges=0,MissingAny=0,MissingBoth=0;
|
||||||
|
std::array<std::array<int32,4>,8> Examples{};
|
||||||
|
size_t ExampleCount=0;
|
||||||
|
const auto Record=[&](uint64 EdgeMask,uint64 CandidateA,uint64 CandidateB,
|
||||||
|
int32 X,int32 Y,int32 Z,int32 Axis)
|
||||||
|
{
|
||||||
|
CheckedEdges+=std::popcount(EdgeMask);
|
||||||
|
const uint64 Missing=EdgeMask&~(CandidateA&CandidateB);
|
||||||
|
uint64 AnyMissing=Missing;
|
||||||
|
MissingAny+=std::popcount(Missing);
|
||||||
|
MissingBoth+=std::popcount(EdgeMask&~(CandidateA|CandidateB));
|
||||||
|
while(AnyMissing&&ExampleCount<Examples.size())
|
||||||
|
{
|
||||||
|
const uint32 Bit=std::countr_zero(AnyMissing);AnyMissing&=AnyMissing-1;
|
||||||
|
Examples[ExampleCount++]={X,Y,Z+static_cast<int32>(Bit),Axis};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const size_t Columns=static_cast<size_t>(Size[0])*Size[1];
|
||||||
|
for(size_t Line=0;Line<Columns;++Line)
|
||||||
|
{
|
||||||
|
const int32 X=Min[0]+static_cast<int32>(Line%Size[0]);
|
||||||
|
const int32 Y=Min[1]+static_cast<int32>(Line/Size[0]);
|
||||||
|
const size_t Base=Line*WordsPerZ;
|
||||||
|
for(size_t W=0;W<WordsPerZ;++W)
|
||||||
|
{
|
||||||
|
const size_t Word=Base+W;
|
||||||
|
const uint64 Rock=RockWords[Word],Candidates=CandidateWords[Word];
|
||||||
|
const uint64 PreviousRock=W?RockWords[Word-1]>>63:0;
|
||||||
|
const uint64 PreviousCandidates=W?CandidateWords[Word-1]>>63:0;
|
||||||
|
uint64 Valid=(Size[2]-W*64)>=64?~uint64{0}
|
||||||
|
:((uint64{1}<<(Size[2]-W*64))-1);
|
||||||
|
if(W==0)Valid&=~uint64{1};
|
||||||
|
const uint64 ShiftedRock=(Rock<<1)|PreviousRock;
|
||||||
|
const uint64 ShiftedCandidates=(Candidates<<1)|PreviousCandidates;
|
||||||
|
Record((Rock^ShiftedRock)&Valid,Candidates,ShiftedCandidates,
|
||||||
|
X,Y,Min[2]+static_cast<int32>(W*64),2);
|
||||||
|
|
||||||
|
if(X+1<Max[0]+1)
|
||||||
|
{
|
||||||
|
const size_t Other=Word+WordsPerZ;
|
||||||
|
Record(Rock^RockWords[Other],Candidates,CandidateWords[Other],
|
||||||
|
X,Y,Min[2]+static_cast<int32>(W*64),0);
|
||||||
|
}
|
||||||
|
if(Y+1<Max[1]+1)
|
||||||
|
{
|
||||||
|
const size_t Other=Word+static_cast<size_t>(Size[0])*WordsPerZ;
|
||||||
|
Record(Rock^RockWords[Other],Candidates,CandidateWords[Other],
|
||||||
|
X,Y,Min[2]+static_cast<int32>(W*64),1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::printf("[GridStairDistanceAudit] region=(%d,%d,%d)..(%d,%d,%d) sign_edges=%llu candidate_fallback_edges=%llu both_fallback_edges=%llu status=%s\n",
|
||||||
|
Min[0],Min[1],Min[2],Max[0],Max[1],Max[2],
|
||||||
|
static_cast<unsigned long long>(CheckedEdges),
|
||||||
|
static_cast<unsigned long long>(MissingAny),
|
||||||
|
static_cast<unsigned long long>(MissingBoth),MissingAny==0?"PASS":"FAIL");
|
||||||
|
const auto CandidateAt=[&](int32 X,int32 Y,int32 Z)
|
||||||
|
{
|
||||||
|
if(!Contains(X,Y,Z))return false;
|
||||||
|
const size_t Column=static_cast<size_t>(Y-Min[1])*Size[0]+static_cast<size_t>(X-Min[0]);
|
||||||
|
const size_t Zi=static_cast<size_t>(Z-Min[2]);
|
||||||
|
return (CandidateWords[Column*WordsPerZ+(Zi>>6)]&(uint64{1}<<(Zi&63u)))!=0;
|
||||||
|
};
|
||||||
|
for(size_t I=0;I<ExampleCount;++I)
|
||||||
|
{
|
||||||
|
int32 AX=Examples[I][0],AY=Examples[I][1],AZ=Examples[I][2];
|
||||||
|
int32 BX=AX,BY=AY,BZ=AZ;
|
||||||
|
if(Examples[I][3]==0)++BX;else if(Examples[I][3]==1)++BY;else --BZ;
|
||||||
|
const float AD=DensityAtLattice(AX,AY,AZ),BD=DensityAtLattice(BX,BY,BZ);
|
||||||
|
const float AExact=AnalyticDensityAt(static_cast<float>(AX),static_cast<float>(AY),static_cast<float>(AZ));
|
||||||
|
const float BExact=AnalyticDensityAt(static_cast<float>(BX),static_cast<float>(BY),static_cast<float>(BZ));
|
||||||
|
std::printf("[GridStairDistanceCandidate] a=(%d,%d,%d) b=(%d,%d,%d) axis=%d candidate=(%d,%d) solid=(%d,%d) density=(%.4f,%.4f) exact=(%.4f,%.4f) critical_air=(%d,%d)\n",
|
||||||
|
AX,AY,AZ,BX,BY,BZ,Examples[I][3],CandidateAt(AX,AY,AZ),CandidateAt(BX,BY,BZ),
|
||||||
|
IsRock(AX,AY,AZ),IsRock(BX,BY,BZ),
|
||||||
|
AD,BD,AExact,BExact,
|
||||||
|
IsInsideCriticalAir(AX,AY,AZ),IsInsideCriticalAir(BX,BY,BZ));
|
||||||
|
}
|
||||||
|
return MissingAny==0;
|
||||||
|
}
|
||||||
|
|
||||||
bool AuditViewSurface(double CameraX,double CameraY,double CameraZ,
|
bool AuditViewSurface(double CameraX,double CameraY,double CameraZ,
|
||||||
float Yaw,float Pitch) const
|
float Yaw,float Pitch) const
|
||||||
{
|
{
|
||||||
@@ -2812,6 +2896,107 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64 CompleteSignEdgeDistances()
|
||||||
|
{
|
||||||
|
uint64 AddedTotal=0,SignCorrections=0;
|
||||||
|
const size_t Columns=static_cast<size_t>(Size[0])*Size[1];
|
||||||
|
for(int32 Pass=0;Pass<4;++Pass)
|
||||||
|
{
|
||||||
|
uint64 AddedThisPass=0;
|
||||||
|
bool bSignChanged=false;
|
||||||
|
const auto EnsureDistance=[&](int32 X,int32 Y,int32 Z)
|
||||||
|
{
|
||||||
|
const size_t Column=static_cast<size_t>(Y-Min[1])*Size[0]
|
||||||
|
+static_cast<size_t>(X-Min[0]);
|
||||||
|
const size_t Zi=static_cast<size_t>(Z-Min[2]);
|
||||||
|
const size_t Word=Column*WordsPerZ+(Zi>>6);
|
||||||
|
const uint64 Mask=uint64{1}<<(Zi&63u);
|
||||||
|
if(CandidateWords[Word]&Mask)return;
|
||||||
|
|
||||||
|
const float Density=AnalyticDensityAt(static_cast<float>(X),
|
||||||
|
static_cast<float>(Y),static_cast<float>(Z));
|
||||||
|
const size_t Node=(Zi*static_cast<size_t>(Size[1])
|
||||||
|
+static_cast<size_t>(Y-Min[1]))*Size[0]
|
||||||
|
+static_cast<size_t>(X-Min[0]);
|
||||||
|
StoreSurfaceDistance(Node,Density);
|
||||||
|
CandidateWords[Word]|=Mask;
|
||||||
|
++AddedThisPass;
|
||||||
|
|
||||||
|
const bool bExactRock=Density<0.0f;
|
||||||
|
const bool bPackedRock=(RockWords[Word]&Mask)!=0;
|
||||||
|
if(bExactRock!=bPackedRock)
|
||||||
|
{
|
||||||
|
if(bExactRock)RockWords[Word]|=Mask;
|
||||||
|
else RockWords[Word]&=~Mask;
|
||||||
|
++SignCorrections;
|
||||||
|
bSignChanged=true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for(size_t Line=0;Line<Columns;++Line)
|
||||||
|
{
|
||||||
|
const int32 X=Min[0]+static_cast<int32>(Line%Size[0]);
|
||||||
|
const int32 Y=Min[1]+static_cast<int32>(Line/Size[0]);
|
||||||
|
const size_t Base=Line*WordsPerZ;
|
||||||
|
for(size_t W=0;W<WordsPerZ;++W)
|
||||||
|
{
|
||||||
|
const size_t Word=Base+W;
|
||||||
|
const uint64 Rock=RockWords[Word],Candidates=CandidateWords[Word];
|
||||||
|
const uint64 PreviousRock=W?RockWords[Word-1]>>63:0;
|
||||||
|
const uint64 PreviousCandidates=W?CandidateWords[Word-1]>>63:0;
|
||||||
|
uint64 Valid=(Size[2]-W*64)>=64?~uint64{0}
|
||||||
|
:((uint64{1}<<(Size[2]-W*64))-1);
|
||||||
|
if(W==0)Valid&=~uint64{1};
|
||||||
|
|
||||||
|
const uint64 ShiftedRock=(Rock<<1)|PreviousRock;
|
||||||
|
const uint64 ShiftedCandidates=(Candidates<<1)|PreviousCandidates;
|
||||||
|
uint64 Missing=((Rock^ShiftedRock)&Valid)
|
||||||
|
&~(Candidates&ShiftedCandidates);
|
||||||
|
while(Missing)
|
||||||
|
{
|
||||||
|
const uint32 Bit=std::countr_zero(Missing);Missing&=Missing-1;
|
||||||
|
const int32 Z=Min[2]+static_cast<int32>(W*64+Bit);
|
||||||
|
EnsureDistance(X,Y,Z);
|
||||||
|
EnsureDistance(X,Y,Z-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(X<Max[0])
|
||||||
|
{
|
||||||
|
const size_t Other=Word+WordsPerZ;
|
||||||
|
Missing=(Rock^RockWords[Other])&Valid
|
||||||
|
&~(Candidates&CandidateWords[Other]);
|
||||||
|
while(Missing)
|
||||||
|
{
|
||||||
|
const uint32 Bit=std::countr_zero(Missing);Missing&=Missing-1;
|
||||||
|
const int32 Z=Min[2]+static_cast<int32>(W*64+Bit);
|
||||||
|
EnsureDistance(X,Y,Z);
|
||||||
|
EnsureDistance(X+1,Y,Z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(Y<Max[1])
|
||||||
|
{
|
||||||
|
const size_t Other=Word+static_cast<size_t>(Size[0])*WordsPerZ;
|
||||||
|
Missing=(Rock^RockWords[Other])&Valid
|
||||||
|
&~(Candidates&CandidateWords[Other]);
|
||||||
|
while(Missing)
|
||||||
|
{
|
||||||
|
const uint32 Bit=std::countr_zero(Missing);Missing&=Missing-1;
|
||||||
|
const int32 Z=Min[2]+static_cast<int32>(W*64+Bit);
|
||||||
|
EnsureDistance(X,Y,Z);
|
||||||
|
EnsureDistance(X,Y+1,Z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddedTotal+=AddedThisPass;
|
||||||
|
if(!bSignChanged)break;
|
||||||
|
}
|
||||||
|
std::printf("[GridStairDistanceRepair] exact_endpoint_proxies=%llu occupancy_corrections=%llu\n",
|
||||||
|
static_cast<unsigned long long>(AddedTotal),
|
||||||
|
static_cast<unsigned long long>(SignCorrections));
|
||||||
|
return AddedTotal;
|
||||||
|
}
|
||||||
|
|
||||||
void StoreSurfaceDistance(size_t Node,float Density)
|
void StoreSurfaceDistance(size_t Node,float Density)
|
||||||
{
|
{
|
||||||
int32 Q=static_cast<int32>(std::lround(Density*16.0f));
|
int32 Q=static_cast<int32>(std::lround(Density*16.0f));
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
#include "StandaloneSnapshotAdapter.h"
|
#include "StandaloneSnapshotAdapter.h"
|
||||||
#include "VoxelForgeCore/VoxelForgeCore.h"
|
#include "VoxelForgeCore/VoxelForgeCore.h"
|
||||||
|
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <mutex>
|
||||||
|
#include <atomic>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace vfcore::detail
|
namespace vfcore::detail
|
||||||
{
|
{
|
||||||
#include "FarShellsInternal.h"
|
#include "FarShellsInternal.h"
|
||||||
@@ -29,6 +35,40 @@ namespace VoxelForgeCore
|
|||||||
TileKey FromNative(const FVoxelTileKey& value)
|
TileKey FromNative(const FVoxelTileKey& value)
|
||||||
{ return {{value.Coord.X,value.Coord.Y,value.Coord.Z},value.Level}; }
|
{ return {{value.Coord.X,value.Coord.Y,value.Coord.Z},value.Level}; }
|
||||||
|
|
||||||
|
std::int32_t FloorDiv(std::int32_t value,std::int32_t divisor)
|
||||||
|
{
|
||||||
|
const std::int32_t quotient=value/divisor,remainder=value%divisor;
|
||||||
|
return quotient-((remainder!=0&&remainder<0)?1:0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int32_t TilesPerShapePage(std::int32_t lod)
|
||||||
|
{
|
||||||
|
constexpr std::int32_t PageWidthVoxels=128;
|
||||||
|
return std::max(1,(PageWidthVoxels/CHUNK_SIZE)>>std::clamp(lod,0,30));
|
||||||
|
}
|
||||||
|
|
||||||
|
TileKey ShapePageKey(const TileKey& tile)
|
||||||
|
{
|
||||||
|
TileKey page=tile;
|
||||||
|
const std::int32_t span=TilesPerShapePage(tile.lod);
|
||||||
|
page.coordinate={FloorDiv(tile.coordinate.x,span),
|
||||||
|
FloorDiv(tile.coordinate.y,span),FloorDiv(tile.coordinate.z,span)};
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CoveredByBaseShape(const ShapeBuildRequest& request,
|
||||||
|
const WorldConfiguration& configuration,const TileKey& tile)
|
||||||
|
{
|
||||||
|
if(tile.lod<0||tile.lod>=configuration.fullResolutionLodCount)return false;
|
||||||
|
const FVoxelTileKey native=ToNative(tile);
|
||||||
|
const FIntVector origin=native.OriginVoxels();
|
||||||
|
const std::int32_t extent=native.ExtentVoxels();
|
||||||
|
for(std::int32_t axis=0;axis<3;++axis)
|
||||||
|
if((&origin.X)[axis]<(&request.minVoxel.x)[axis]
|
||||||
|
||(&origin.X)[axis]+extent>(&request.maxVoxel.x)[axis])return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
WorldConfiguration MakeConfiguration(const FStandaloneOwnerWorld& owner)
|
WorldConfiguration MakeConfiguration(const FStandaloneOwnerWorld& owner)
|
||||||
{
|
{
|
||||||
WorldConfiguration out;
|
WorldConfiguration out;
|
||||||
@@ -233,6 +273,10 @@ namespace VoxelForgeCore
|
|||||||
std::shared_ptr<FStandaloneOwnerWorld> owner;
|
std::shared_ptr<FStandaloneOwnerWorld> owner;
|
||||||
WorldConfiguration configuration;
|
WorldConfiguration configuration;
|
||||||
std::unique_ptr<FStandaloneShapeWorld> shape;
|
std::unique_ptr<FStandaloneShapeWorld> shape;
|
||||||
|
ShapeBuildRequest buildRequest;
|
||||||
|
mutable std::shared_mutex tileRegionMutex;
|
||||||
|
std::unordered_map<TileKey,std::shared_ptr<FStandaloneShapeWorld>,TileKeyHash> tileRegions;
|
||||||
|
std::atomic<std::size_t> tileRegionCount{0};
|
||||||
std::vector<FStandaloneShapeWorld::FEditBrush> editLog;
|
std::vector<FStandaloneShapeWorld::FEditBrush> editLog;
|
||||||
std::map<std::uint64_t,EditResult> transactions;
|
std::map<std::uint64_t,EditResult> transactions;
|
||||||
bool hasLastSequence=false;
|
bool hasLastSequence=false;
|
||||||
@@ -259,10 +303,88 @@ namespace VoxelForgeCore
|
|||||||
ToNative(request.clipMinVoxel),ToNative(request.clipMaxVoxel),
|
ToNative(request.clipMinVoxel),ToNative(request.clipMaxVoxel),
|
||||||
static_cast<int32>(std::clamp<std::uint32_t>(request.workerLimit,1,8)),ToNative(request.detail),
|
static_cast<int32>(std::clamp<std::uint32_t>(request.workerLimit,1,8)),ToNative(request.detail),
|
||||||
request.includeFarWorld,error,&scheduler,request.roundTunnels,request.profileDetailBand);
|
request.includeFarWorld,error,&scheduler,request.roundTunnels,request.profileDetailBand);
|
||||||
if(ok)impl_->shape=std::move(shape);
|
if(ok)
|
||||||
|
{
|
||||||
|
impl_->shape=std::move(shape);
|
||||||
|
impl_->buildRequest=request;
|
||||||
|
std::unique_lock lock(impl_->tileRegionMutex);
|
||||||
|
impl_->tileRegions.clear();
|
||||||
|
impl_->tileRegionCount.store(0,std::memory_order_release);
|
||||||
|
}
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool World::compileTileRegion(std::span<const TileKey> tiles,std::uint32_t workerLimit,
|
||||||
|
const JobScheduler& scheduler,std::string& error,
|
||||||
|
std::size_t* outCompiledTiles)
|
||||||
|
{
|
||||||
|
if(outCompiledTiles)*outCompiledTiles=0;
|
||||||
|
if(!impl_||!impl_->owner||!impl_->shape||!impl_->shape->IsBuilt())
|
||||||
|
{error="the base world must be compiled before adding tile regions";return false;}
|
||||||
|
if(tiles.empty())return true;
|
||||||
|
|
||||||
|
std::unordered_map<TileKey,std::vector<TileKey>,TileKeyHash> pendingPages;
|
||||||
|
pendingPages.reserve(tiles.size());
|
||||||
|
{
|
||||||
|
std::shared_lock lock(impl_->tileRegionMutex);
|
||||||
|
for(const TileKey& key:tiles)
|
||||||
|
{
|
||||||
|
if(key.lod<0||key.lod>8)
|
||||||
|
{error="tile region contains an invalid LOD";return false;}
|
||||||
|
if(key.lod>=impl_->configuration.fullResolutionLodCount
|
||||||
|
||CoveredByBaseShape(impl_->buildRequest,impl_->configuration,key))continue;
|
||||||
|
const TileKey pageKey=ShapePageKey(key);
|
||||||
|
if(!impl_->tileRegions.contains(pageKey))pendingPages[pageKey].push_back(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(pendingPages.empty())return true;
|
||||||
|
|
||||||
|
std::size_t compiledTileCount=0;
|
||||||
|
for(const auto& [pageKey,requestedTiles]:pendingPages)
|
||||||
|
{
|
||||||
|
const std::int32_t tileCount=TilesPerShapePage(pageKey.lod);
|
||||||
|
const std::int32_t tileExtent=CHUNK_SIZE<<pageKey.lod;
|
||||||
|
const FIntVector pageMin(pageKey.coordinate.x*tileCount*tileExtent,
|
||||||
|
pageKey.coordinate.y*tileCount*tileExtent,
|
||||||
|
pageKey.coordinate.z*tileCount*tileExtent);
|
||||||
|
const FIntVector pageMax(pageMin.X+tileCount*tileExtent,
|
||||||
|
pageMin.Y+tileCount*tileExtent,pageMin.Z+tileCount*tileExtent);
|
||||||
|
constexpr std::int32_t PagePad=8;
|
||||||
|
const FIntVector fineMin(pageMin.X-PagePad,pageMin.Y-PagePad,pageMin.Z-PagePad);
|
||||||
|
const FIntVector fineMax(pageMax.X+PagePad,pageMax.Y+PagePad,pageMax.Z+PagePad);
|
||||||
|
|
||||||
|
auto page=std::make_shared<FStandaloneShapeWorld>();
|
||||||
|
const ShapeBuildRequest& base=impl_->buildRequest;
|
||||||
|
if(!page->Build(*impl_->owner,fineMin,fineMax,pageMin,pageMax,
|
||||||
|
static_cast<int32>(std::clamp<std::uint32_t>(workerLimit,1,8)),
|
||||||
|
ToNative(base.detail),false,error,&scheduler,
|
||||||
|
base.roundTunnels,base.profileDetailBand))return false;
|
||||||
|
if(!impl_->editLog.empty())page->SetEditTransaction(impl_->editLog);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock lock(impl_->tileRegionMutex);
|
||||||
|
impl_->tileRegions.try_emplace(pageKey,std::move(page));
|
||||||
|
impl_->tileRegionCount.store(impl_->tileRegions.size(),std::memory_order_release);
|
||||||
|
}
|
||||||
|
compiledTileCount+=requestedTiles.size();
|
||||||
|
}
|
||||||
|
if(outCompiledTiles)*outCompiledTiles=compiledTileCount;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void World::retainTileRegions(std::span<const TileKey> desiredTiles)
|
||||||
|
{
|
||||||
|
if(!impl_)return;
|
||||||
|
std::unordered_set<TileKey,TileKeyHash> desired;
|
||||||
|
desired.reserve(desiredTiles.size()*2+1);
|
||||||
|
for(const TileKey& key:desiredTiles)
|
||||||
|
if(key.lod>=0&&key.lod<impl_->configuration.fullResolutionLodCount)
|
||||||
|
desired.insert(ShapePageKey(key));
|
||||||
|
std::unique_lock lock(impl_->tileRegionMutex);
|
||||||
|
std::erase_if(impl_->tileRegions,[&](const auto& entry){return !desired.contains(entry.first);});
|
||||||
|
impl_->tileRegionCount.store(impl_->tileRegions.size(),std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
void World::buildFarWorld()
|
void World::buildFarWorld()
|
||||||
{ if(impl_->shape)impl_->shape->BuildFarWorld(); }
|
{ if(impl_->shape)impl_->shape->BuildFarWorld(); }
|
||||||
bool World::isCompiled() const noexcept { return impl_&&impl_->shape&&impl_->shape->IsBuilt(); }
|
bool World::isCompiled() const noexcept { return impl_&&impl_->shape&&impl_->shape->IsBuilt(); }
|
||||||
@@ -357,9 +479,17 @@ namespace VoxelForgeCore
|
|||||||
if(!impl_||!impl_->owner||!impl_->shape||!impl_->shape->IsBuilt())
|
if(!impl_||!impl_->owner||!impl_->shape||!impl_->shape->IsBuilt())
|
||||||
{ error="world must be compiled before generating tiles";return std::nullopt; }
|
{ error="world must be compiled before generating tiles";return std::nullopt; }
|
||||||
const auto& world=*impl_->owner;
|
const auto& world=*impl_->owner;
|
||||||
|
std::shared_ptr<FStandaloneShapeWorld> page;
|
||||||
|
if(impl_->tileRegionCount.load(std::memory_order_acquire)!=0)
|
||||||
|
{
|
||||||
|
std::shared_lock lock(impl_->tileRegionMutex);
|
||||||
|
if(const auto found=impl_->tileRegions.find(ShapePageKey(key));found!=impl_->tileRegions.end())
|
||||||
|
page=found->second;
|
||||||
|
}
|
||||||
|
const FStandaloneShapeWorld& tileShape=page?*page:*impl_->shape;
|
||||||
UVoxelMarchingCubesMesher mesher;
|
UVoxelMarchingCubesMesher mesher;
|
||||||
mesher.SetGenerator(world.Generator.get());
|
mesher.SetGenerator(world.Generator.get());
|
||||||
mesher.SetStandaloneShapeWorld(impl_->shape.get());
|
mesher.SetStandaloneShapeWorld(&tileShape);
|
||||||
mesher.bStandaloneSkipUvAndColors=options.skipUvAndColors;
|
mesher.bStandaloneSkipUvAndColors=options.skipUvAndColors;
|
||||||
mesher.bStandaloneSkipUV=options.skipUv;mesher.bStandaloneSkipColors=options.skipColors;
|
mesher.bStandaloneSkipUV=options.skipUv;mesher.bStandaloneSkipColors=options.skipColors;
|
||||||
mesher.bStandaloneSkipMaterialGroups=options.skipMaterialGroups;
|
mesher.bStandaloneSkipMaterialGroups=options.skipMaterialGroups;
|
||||||
@@ -374,10 +504,11 @@ namespace VoxelForgeCore
|
|||||||
FVoxelMeshData mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
|
FVoxelMeshData mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
|
||||||
options.bandMinZVoxel,options.bandMaxZVoxel,&sampleCount,nullptr,nullptr,
|
options.bandMinZVoxel,options.bandMaxZVoxel,&sampleCount,nullptr,nullptr,
|
||||||
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
|
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
|
||||||
if(impl_->shape->HasFarWorld())
|
const FStandaloneShapeWorld& farShape=tileShape.HasFarWorld()?tileShape:*impl_->shape;
|
||||||
|
if(farShape.HasFarWorld())
|
||||||
{
|
{
|
||||||
AppendFarStratumShells(world,*impl_->shape,nativeKey,step,mesh);
|
AppendFarStratumShells(world,farShape,nativeKey,step,mesh);
|
||||||
AppendFarConnectorShells(*impl_->shape,nativeKey,step,world.CoarseTileCells,mesh);
|
AppendFarConnectorShells(farShape,nativeKey,step,world.CoarseTileCells,mesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
TileMesh out;out.key=key;out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount));
|
TileMesh out;out.key=key;out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount));
|
||||||
@@ -451,6 +582,11 @@ namespace VoxelForgeCore
|
|||||||
}
|
}
|
||||||
impl_->editLog.insert(impl_->editLog.end(),native.begin(),native.end());
|
impl_->editLog.insert(impl_->editLog.end(),native.begin(),native.end());
|
||||||
impl_->shape->SetEditTransaction(impl_->editLog);
|
impl_->shape->SetEditTransaction(impl_->editLog);
|
||||||
|
{
|
||||||
|
std::shared_lock lock(impl_->tileRegionMutex);
|
||||||
|
for(const auto& [key,page]:impl_->tileRegions)
|
||||||
|
if(page)page->SetEditTransaction(impl_->editLog);
|
||||||
|
}
|
||||||
const FIntVector min=impl_->shape->GetEditBoundsMin(),max=impl_->shape->GetEditBoundsMax();
|
const FIntVector min=impl_->shape->GetEditBoundsMin(),max=impl_->shape->GetEditBoundsMax();
|
||||||
EditResult result;result.digest=digest;
|
EditResult result;result.digest=digest;
|
||||||
result.affectedByLod.resize(static_cast<std::size_t>(std::max(impl_->configuration.maxLod,0)+1));
|
result.affectedByLod.resize(static_cast<std::size_t>(std::max(impl_->configuration.maxLod,0)+1));
|
||||||
@@ -487,6 +623,8 @@ namespace VoxelForgeCore
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool World::auditDetailPassing() const { return impl_&&impl_->shape&&impl_->shape->IsDetailAuditPassing(); }
|
bool World::auditDetailPassing() const { return impl_&&impl_->shape&&impl_->shape->IsDetailAuditPassing(); }
|
||||||
|
bool World::auditGridStairIntegrity() const
|
||||||
|
{ return impl_&&impl_->shape&&impl_->shape->AuditGridStairIntegrity(); }
|
||||||
bool World::auditSurfaceFamilies(bool passageOnly) const
|
bool World::auditSurfaceFamilies(bool passageOnly) const
|
||||||
{ return impl_&&impl_->shape&&impl_->shape->AuditSurfaceBandFamilies(passageOnly); }
|
{ return impl_&&impl_->shape&&impl_->shape->AuditSurfaceBandFamilies(passageOnly); }
|
||||||
bool World::auditViewSurface(Vec3d camera,double yaw,double pitch) const
|
bool World::auditViewSurface(Vec3d camera,double yaw,double pitch) const
|
||||||
|
|||||||
@@ -207,6 +207,170 @@ namespace
|
|||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FCoarsePanelAudit
|
||||||
|
{
|
||||||
|
uint64 Tiles=0,PlanarOpenComponents=0,OffSurfacePanels=0,Triangles=0;
|
||||||
|
double MaximumSourceErrorVoxels=0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
FCoarsePanelAudit AuditCoarseSurfacePanels(const FStandaloneCpuTile& Tile,
|
||||||
|
const VoxelForgeCore::World& Shape,const VoxelForgeCore::WorldConfiguration& Config,
|
||||||
|
bool bPrintCandidates)
|
||||||
|
{
|
||||||
|
FCoarsePanelAudit Result;
|
||||||
|
const size_t VertexCount=Tile.Vertices.size();
|
||||||
|
const size_t TriangleCount=Tile.Indices.size()/3;
|
||||||
|
if(Tile.Key.Level<1||VertexCount==0||TriangleCount==0)return Result;
|
||||||
|
std::vector<uint32> Parent(VertexCount);
|
||||||
|
std::vector<uint8> Rank(VertexCount,0);
|
||||||
|
std::iota(Parent.begin(),Parent.end(),0u);
|
||||||
|
const auto Find=[&](uint32 Value)
|
||||||
|
{
|
||||||
|
uint32 Root=Value;
|
||||||
|
while(Parent[Root]!=Root)Root=Parent[Root];
|
||||||
|
while(Parent[Value]!=Value)
|
||||||
|
{const uint32 Next=Parent[Value];Parent[Value]=Root;Value=Next;}
|
||||||
|
return Root;
|
||||||
|
};
|
||||||
|
const auto Union=[&](uint32 A,uint32 B)
|
||||||
|
{
|
||||||
|
A=Find(A);B=Find(B);if(A==B)return;
|
||||||
|
if(Rank[A]<Rank[B])std::swap(A,B);
|
||||||
|
Parent[B]=A;if(Rank[A]==Rank[B])++Rank[A];
|
||||||
|
};
|
||||||
|
const auto EdgeKey=[](uint32 A,uint32 B)
|
||||||
|
{if(A>B)std::swap(A,B);return (static_cast<uint64>(A)<<32)|B;};
|
||||||
|
std::unordered_map<uint64,uint8> EdgeUse;
|
||||||
|
EdgeUse.reserve(TriangleCount*3/2+1);
|
||||||
|
for(size_t T=0;T<TriangleCount;++T)
|
||||||
|
{
|
||||||
|
const uint32 A=Tile.Indices[T*3],B=Tile.Indices[T*3+1],C=Tile.Indices[T*3+2];
|
||||||
|
if(A>=VertexCount||B>=VertexCount||C>=VertexCount)continue;
|
||||||
|
Union(A,B);Union(B,C);
|
||||||
|
for(const auto [U,V]:std::array<std::pair<uint32,uint32>,3>{{{A,B},{B,C},{C,A}}})
|
||||||
|
{
|
||||||
|
uint8& Count=EdgeUse[EdgeKey(U,V)];
|
||||||
|
if(Count<255)++Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct FComponent
|
||||||
|
{
|
||||||
|
uint64 Triangles=0,InteriorFreeEdges=0,SourceSamples=0,OffSourceSamples=0;
|
||||||
|
double Normal[3]={0,0,0},NormalWeight=0,MaximumSourceError=0;
|
||||||
|
double WorstPosition[3]={0,0,0};
|
||||||
|
bool bWorstTriangleCentroid=false;
|
||||||
|
};
|
||||||
|
std::vector<FComponent> Components(VertexCount);
|
||||||
|
for(size_t T=0;T<TriangleCount;++T)
|
||||||
|
{
|
||||||
|
const uint32 A=Tile.Indices[T*3],B=Tile.Indices[T*3+1],C=Tile.Indices[T*3+2];
|
||||||
|
if(A>=VertexCount||B>=VertexCount||C>=VertexCount)continue;
|
||||||
|
const uint32 Root=Find(A);FComponent& Stats=Components[Root];++Stats.Triangles;
|
||||||
|
const auto& PA=Tile.Vertices[A].Position;const auto& PB=Tile.Vertices[B].Position;
|
||||||
|
const auto& PC=Tile.Vertices[C].Position;
|
||||||
|
const double U[3]={PB[0]-PA[0],PB[1]-PA[1],PB[2]-PA[2]};
|
||||||
|
const double V[3]={PC[0]-PA[0],PC[1]-PA[1],PC[2]-PA[2]};
|
||||||
|
double N[3]={U[1]*V[2]-U[2]*V[1],U[2]*V[0]-U[0]*V[2],U[0]*V[1]-U[1]*V[0]};
|
||||||
|
const double Area2=std::sqrt(N[0]*N[0]+N[1]*N[1]+N[2]*N[2]);
|
||||||
|
if(Area2>1.0e-12)
|
||||||
|
{
|
||||||
|
for(int32 Axis=0;Axis<3;++Axis)Stats.Normal[Axis]+=N[Axis]/Area2*Area2;
|
||||||
|
Stats.NormalWeight+=Area2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const FIntVector Origin=Tile.Key.OriginVoxels();
|
||||||
|
const double TileMin[3]={static_cast<double>(Origin.X),static_cast<double>(Origin.Y),static_cast<double>(Origin.Z)};
|
||||||
|
const double TileMax[3]={TileMin[0]+Tile.Key.ExtentVoxels(),TileMin[1]+Tile.Key.ExtentVoxels(),TileMin[2]+Tile.Key.ExtentVoxels()};
|
||||||
|
const auto IsTileBoundaryEdge=[&](uint32 A,uint32 B)
|
||||||
|
{
|
||||||
|
for(int32 Axis=0;Axis<3;++Axis)
|
||||||
|
{
|
||||||
|
const double PA=Tile.Vertices[A].Position[Axis]*4.0;
|
||||||
|
const double PB=Tile.Vertices[B].Position[Axis]*4.0;
|
||||||
|
if((std::abs(PA-TileMin[Axis])<0.75&&std::abs(PB-TileMin[Axis])<0.75)
|
||||||
|
||(std::abs(PA-TileMax[Axis])<0.75&&std::abs(PB-TileMax[Axis])<0.75))return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
for(const auto& [Key,Count]:EdgeUse)
|
||||||
|
{
|
||||||
|
if(Count!=1)continue;
|
||||||
|
const uint32 A=static_cast<uint32>(Key>>32),B=static_cast<uint32>(Key);
|
||||||
|
if(A>=VertexCount||B>=VertexCount||IsTileBoundaryEdge(A,B))continue;
|
||||||
|
++Components[Find(A)].InteriorFreeEdges;
|
||||||
|
}
|
||||||
|
const int32 Extent=Tile.Key.ExtentVoxels();
|
||||||
|
const int32 Cells=Tile.Key.Level<Config.fullResolutionLodCount
|
||||||
|
?CHUNK_SIZE:std::max(1,Config.coarseTileCells);
|
||||||
|
const double Step=std::max(1,Extent/Cells);
|
||||||
|
const double ResidualLimit=std::max(2.0,Step*0.10);
|
||||||
|
std::vector<uint8> Candidate(VertexCount,0);
|
||||||
|
for(size_t Root=0;Root<VertexCount;++Root)
|
||||||
|
{
|
||||||
|
FComponent& Stats=Components[Root];
|
||||||
|
if(Stats.Triangles==0||Stats.Triangles>256||Stats.InteriorFreeEdges==0||Stats.NormalWeight<=0.0)continue;
|
||||||
|
const double NormalLength=std::sqrt(Stats.Normal[0]*Stats.Normal[0]
|
||||||
|
+Stats.Normal[1]*Stats.Normal[1]+Stats.Normal[2]*Stats.Normal[2]);
|
||||||
|
const double Coherence=NormalLength/Stats.NormalWeight;
|
||||||
|
if(Coherence<0.995)continue;
|
||||||
|
Candidate[Root]=1;++Result.PlanarOpenComponents;
|
||||||
|
}
|
||||||
|
if(Result.PlanarOpenComponents==0)return Result;
|
||||||
|
for(size_t Vertex=0;Vertex<VertexCount;++Vertex)
|
||||||
|
{
|
||||||
|
const uint32 Root=Find(static_cast<uint32>(Vertex));
|
||||||
|
if(!Candidate[Root])continue;
|
||||||
|
const auto& P=Tile.Vertices[Vertex].Position;
|
||||||
|
const double Error=std::abs(Shape.densityAt({P[0]*4.0,P[1]*4.0,P[2]*4.0}));
|
||||||
|
FComponent& Stats=Components[Root];++Stats.SourceSamples;
|
||||||
|
if(Error>Stats.MaximumSourceError)
|
||||||
|
{
|
||||||
|
Stats.MaximumSourceError=Error;
|
||||||
|
std::copy_n(P,3,Stats.WorstPosition);
|
||||||
|
Stats.bWorstTriangleCentroid=false;
|
||||||
|
}
|
||||||
|
if(Error>ResidualLimit)++Stats.OffSourceSamples;
|
||||||
|
}
|
||||||
|
for(size_t T=0;T<TriangleCount;++T)
|
||||||
|
{
|
||||||
|
const uint32 A=Tile.Indices[T*3],B=Tile.Indices[T*3+1],C=Tile.Indices[T*3+2];
|
||||||
|
if(A>=VertexCount||B>=VertexCount||C>=VertexCount)continue;
|
||||||
|
const uint32 Root=Find(A);if(!Candidate[Root])continue;
|
||||||
|
const auto& PA=Tile.Vertices[A].Position;const auto& PB=Tile.Vertices[B].Position;
|
||||||
|
const auto& PC=Tile.Vertices[C].Position;
|
||||||
|
const double Error=std::abs(Shape.densityAt({(PA[0]+PB[0]+PC[0])*4.0/3.0,
|
||||||
|
(PA[1]+PB[1]+PC[1])*4.0/3.0,(PA[2]+PB[2]+PC[2])*4.0/3.0}));
|
||||||
|
FComponent& Stats=Components[Root];++Stats.SourceSamples;
|
||||||
|
if(Error>Stats.MaximumSourceError)
|
||||||
|
{
|
||||||
|
Stats.MaximumSourceError=Error;
|
||||||
|
Stats.WorstPosition[0]=(PA[0]+PB[0]+PC[0])/3.0;
|
||||||
|
Stats.WorstPosition[1]=(PA[1]+PB[1]+PC[1])/3.0;
|
||||||
|
Stats.WorstPosition[2]=(PA[2]+PB[2]+PC[2])/3.0;
|
||||||
|
Stats.bWorstTriangleCentroid=true;
|
||||||
|
}
|
||||||
|
if(Error>ResidualLimit)++Stats.OffSourceSamples;
|
||||||
|
}
|
||||||
|
for(size_t Root=0;Root<VertexCount;++Root)
|
||||||
|
{
|
||||||
|
const FComponent& Stats=Components[Root];if(!Candidate[Root]||Stats.SourceSamples==0)continue;
|
||||||
|
const double OffShare=static_cast<double>(Stats.OffSourceSamples)/Stats.SourceSamples;
|
||||||
|
Result.MaximumSourceErrorVoxels=std::max(Result.MaximumSourceErrorVoxels,Stats.MaximumSourceError);
|
||||||
|
if(OffShare<0.25)continue;
|
||||||
|
++Result.OffSurfacePanels;Result.Triangles+=Stats.Triangles;
|
||||||
|
if(bPrintCandidates&&Result.OffSurfacePanels<=8)
|
||||||
|
std::printf("[CoarsePanelCandidate] lod=%d tile=(%d,%d,%d) triangles=%llu interior_free_edges=%llu source_error=%.3f vox off_share=%.1f%% limit=%.3f vox worst=(%.2f,%.2f,%.2f)m sample=%s\n",
|
||||||
|
Tile.Key.Level,Tile.Key.Coord.X,Tile.Key.Coord.Y,Tile.Key.Coord.Z,
|
||||||
|
static_cast<unsigned long long>(Stats.Triangles),
|
||||||
|
static_cast<unsigned long long>(Stats.InteriorFreeEdges),Stats.MaximumSourceError,
|
||||||
|
OffShare*100.0,ResidualLimit,
|
||||||
|
Stats.WorstPosition[0],Stats.WorstPosition[1],Stats.WorstPosition[2],
|
||||||
|
Stats.bWorstTriangleCentroid?"centroid":"vertex");
|
||||||
|
}
|
||||||
|
Result.Tiles=1;
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
struct M4 { float M[4][4]{}; };
|
struct M4 { float M[4][4]{}; };
|
||||||
M4 Identity(){M4 R{};for(int I=0;I<4;++I)R.M[I][I]=1.0f;return R;}
|
M4 Identity(){M4 R{};for(int I=0;I<4;++I)R.M[I][I]=1.0f;return R;}
|
||||||
M4 Transpose(M4 A){M4 B{};for(int I=0;I<4;++I)for(int J=0;J<4;++J)B.M[I][J]=A.M[J][I];return B;}
|
M4 Transpose(M4 A){M4 B{};for(int I=0;I<4;++I)for(int J=0;J<4;++J)B.M[I][J]=A.M[J][I];return B;}
|
||||||
@@ -277,6 +441,8 @@ namespace
|
|||||||
uint64 Tiles=0;
|
uint64 Tiles=0;
|
||||||
uint64 Samples=0;
|
uint64 Samples=0;
|
||||||
double WorkerSeconds=0.0;
|
double WorkerSeconds=0.0;
|
||||||
|
double CorePageSeconds=0.0;
|
||||||
|
uint64 PageCoveredTiles=0;
|
||||||
std::chrono::steady_clock::time_point StartTime{};
|
std::chrono::steady_clock::time_point StartTime{};
|
||||||
bool bReported=false;
|
bool bReported=false;
|
||||||
};
|
};
|
||||||
@@ -357,6 +523,7 @@ namespace
|
|||||||
V3 CameraPositionOverride{};
|
V3 CameraPositionOverride{};
|
||||||
float CameraYawOverride=0.0f,CameraPitchOverride=0.0f;
|
float CameraYawOverride=0.0f,CameraPitchOverride=0.0f;
|
||||||
std::unordered_map<FVoxelTileKey,FResidentTileMesh,FStandaloneTileKeyHash> ResidentTiles;
|
std::unordered_map<FVoxelTileKey,FResidentTileMesh,FStandaloneTileKeyHash> ResidentTiles;
|
||||||
|
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> DeferredLeaverTiles;
|
||||||
std::unordered_map<FStandaloneBatchKey,std::vector<FVoxelTileKey>,FStandaloneBatchKeyHash> BatchMembers;
|
std::unordered_map<FStandaloneBatchKey,std::vector<FVoxelTileKey>,FStandaloneBatchKeyHash> BatchMembers;
|
||||||
std::unordered_map<FStandaloneBatchKey,FStreamingGpuBatch,FStandaloneBatchKeyHash> StreamingBatches;
|
std::unordered_map<FStandaloneBatchKey,FStreamingGpuBatch,FStandaloneBatchKeyHash> StreamingBatches;
|
||||||
std::unordered_set<FStandaloneBatchKey,FStandaloneBatchKeyHash> DirtyBatchSet;
|
std::unordered_set<FStandaloneBatchKey,FStandaloneBatchKeyHash> DirtyBatchSet;
|
||||||
@@ -441,6 +608,7 @@ namespace
|
|||||||
}
|
}
|
||||||
StreamingBatches.clear();BatchMembers.clear();ResidentTiles.clear();
|
StreamingBatches.clear();BatchMembers.clear();ResidentTiles.clear();
|
||||||
DirtyBatchSet.clear();DesiredTiles.clear();CrossingWork.clear();
|
DirtyBatchSet.clear();DesiredTiles.clear();CrossingWork.clear();
|
||||||
|
DeferredLeaverTiles.clear();
|
||||||
for(int32 I=0;I<=MaxLevels;++I)
|
for(int32 I=0;I<=MaxLevels;++I)
|
||||||
{DesiredByLevel[I]=0;GeneratedByLevel[I]=0;SamplesByLevel[I]=0;WorkerSecondsByLevel[I]=0.0;
|
{DesiredByLevel[I]=0;GeneratedByLevel[I]=0;SamplesByLevel[I]=0;WorkerSecondsByLevel[I]=0.0;
|
||||||
std::fill(std::begin(ProfileCyclesByLevel[I]),std::end(ProfileCyclesByLevel[I]),0);
|
std::fill(std::begin(ProfileCyclesByLevel[I]),std::end(ProfileCyclesByLevel[I]),0);
|
||||||
@@ -1019,25 +1187,69 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
if(!World||!Streamer)return;
|
if(!World||!Streamer)return;
|
||||||
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
|
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
|
||||||
FStandaloneDesiredTileSet NewDesired=StandaloneWorldMesher::BuildDesiredSet(*World,CameraVoxels);
|
FStandaloneDesiredTileSet NewDesired=StandaloneWorldMesher::BuildDesiredSet(*World,CameraVoxels);
|
||||||
if(NewDesired.CenterChunk==BuildTargetChunk)return;
|
if(bShapeWorld&&ShapeWorld)
|
||||||
if(bShapeWorld)
|
|
||||||
{
|
{
|
||||||
bRebuildPending=true;
|
const auto CoreTiles=ShapeWorld->desiredTiles(
|
||||||
std::printf("[ShapeRegion] camera crossed its compiled full-detail bounds; rebuilding the interval region.\n");
|
{CameraVoxels.X,CameraVoxels.Y,CameraVoxels.Z});
|
||||||
return;
|
NewDesired.Tiles.clear();NewDesired.Tiles.reserve(CoreTiles.size());
|
||||||
|
for(const auto& Key:CoreTiles)
|
||||||
|
NewDesired.Tiles.emplace_back(
|
||||||
|
FIntVector(Key.coordinate.x,Key.coordinate.y,Key.coordinate.z),Key.lod);
|
||||||
}
|
}
|
||||||
|
if(NewDesired.CenterChunk==BuildTargetChunk)return;
|
||||||
const FIntVector Previous=BuildTargetChunk;
|
const FIntVector Previous=BuildTargetChunk;
|
||||||
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> NewSet;
|
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> NewSet;
|
||||||
NewSet.reserve(NewDesired.Tiles.size()*2+1);
|
NewSet.reserve(NewDesired.Tiles.size()*2+1);
|
||||||
for(const FVoxelTileKey& Key:NewDesired.Tiles)NewSet.insert(Key);
|
for(const FVoxelTileKey& Key:NewDesired.Tiles)NewSet.insert(Key);
|
||||||
std::vector<FVoxelTileKey> Leavers;
|
std::vector<FVoxelTileKey> Leavers;
|
||||||
for(const auto& Pair:ResidentTiles)if(!NewSet.contains(Pair.first))Leavers.push_back(Pair.first);
|
for(const auto& Pair:ResidentTiles)if(!NewSet.contains(Pair.first))Leavers.push_back(Pair.first);
|
||||||
|
const auto CrossingStart=std::chrono::steady_clock::now();
|
||||||
|
double PageCompileSeconds=0.0;
|
||||||
|
uint64 PageCoveredTiles=0;
|
||||||
|
if(bShapeWorld&&ShapeWorld)
|
||||||
|
{
|
||||||
|
std::vector<VoxelForgeCore::TileKey> NewCoreTiles;
|
||||||
|
NewCoreTiles.reserve(NewDesired.Tiles.size());
|
||||||
|
for(const FVoxelTileKey& Key:NewDesired.Tiles)
|
||||||
|
if(!ResidentTiles.contains(Key))
|
||||||
|
NewCoreTiles.push_back({{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level});
|
||||||
|
std::string PageError;
|
||||||
|
size_t CompiledPageCount=0;
|
||||||
|
if(!ShapeWorld->compileTileRegion(NewCoreTiles,
|
||||||
|
static_cast<uint32>(std::min(4,WorkerCount)),CoreScheduler,PageError,
|
||||||
|
&CompiledPageCount))
|
||||||
|
{
|
||||||
|
std::fprintf(stderr,"[ShapeRegionPage] compile failed at center=(%d,%d,%d): %s\n",
|
||||||
|
NewDesired.CenterChunk.X,NewDesired.CenterChunk.Y,NewDesired.CenterChunk.Z,
|
||||||
|
PageError.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PageCoveredTiles=static_cast<uint64>(CompiledPageCount);
|
||||||
|
std::vector<VoxelForgeCore::TileKey> CoreDesired;
|
||||||
|
CoreDesired.reserve(NewDesired.Tiles.size());
|
||||||
|
for(const FVoxelTileKey& Key:NewDesired.Tiles)
|
||||||
|
CoreDesired.push_back({{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level});
|
||||||
|
ShapeWorld->retainTileRegions(CoreDesired);
|
||||||
|
for(const FVoxelTileKey& Key:Leavers)DeferredLeaverTiles.insert(Key);
|
||||||
|
const double PageSeconds=std::chrono::duration<double>(
|
||||||
|
std::chrono::steady_clock::now()-CrossingStart).count();
|
||||||
|
PageCompileSeconds=PageSeconds;
|
||||||
|
std::printf("[ShapeRegionPage] center=(%d,%d,%d) requested_tiles=%zu page_covered_tiles=%llu worker_limit=%d seconds=%.4f retained_resident=%zu deferred_leavers=%zu\n",
|
||||||
|
NewDesired.CenterChunk.X,NewDesired.CenterChunk.Y,NewDesired.CenterChunk.Z,
|
||||||
|
NewCoreTiles.size(),static_cast<unsigned long long>(PageCoveredTiles),
|
||||||
|
std::min(4,WorkerCount),PageSeconds,
|
||||||
|
ResidentTiles.size()-std::min(ResidentTiles.size(),Leavers.size()),Leavers.size());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
for(const FVoxelTileKey& Key:Leavers)RemoveResidentTile(Key);
|
for(const FVoxelTileKey& Key:Leavers)RemoveResidentTile(Key);
|
||||||
|
}
|
||||||
DesiredTiles=std::move(NewDesired.Tiles);
|
DesiredTiles=std::move(NewDesired.Tiles);
|
||||||
for(int32 I=0;I<=MaxLevels;++I)DesiredByLevel[I]=0;
|
for(int32 I=0;I<=MaxLevels;++I)DesiredByLevel[I]=0;
|
||||||
for(const FVoxelTileKey& Tile:DesiredTiles)++DesiredByLevel[Tile.Level];
|
for(const FVoxelTileKey& Tile:DesiredTiles)++DesiredByLevel[Tile.Level];
|
||||||
std::vector<FVoxelTileKey> ResidentKeys;ResidentKeys.reserve(ResidentTiles.size());
|
std::vector<FVoxelTileKey> ResidentKeys;ResidentKeys.reserve(ResidentTiles.size());
|
||||||
for(const auto& Pair:ResidentTiles)ResidentKeys.push_back(Pair.first);
|
for(const auto& Pair:ResidentTiles)
|
||||||
|
if(NewSet.contains(Pair.first))ResidentKeys.push_back(Pair.first);
|
||||||
++CrossingIndex;
|
++CrossingIndex;
|
||||||
const FStandaloneStreamingUpdate Update=Streamer->UpdateDesired(CameraVoxels,ResidentKeys,CrossingIndex);
|
const FStandaloneStreamingUpdate Update=Streamer->UpdateDesired(CameraVoxels,ResidentKeys,CrossingIndex);
|
||||||
for(const auto& Pair:Update.CancelledByCrossing)
|
for(const auto& Pair:Update.CancelledByCrossing)
|
||||||
@@ -1048,12 +1260,14 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
}
|
}
|
||||||
FCrossingWork& Work=CrossingWork[CrossingIndex];
|
FCrossingWork& Work=CrossingWork[CrossingIndex];
|
||||||
Work.SubmittedJobs=Update.NewJobs;Work.PendingJobs=Update.NewJobs;
|
Work.SubmittedJobs=Update.NewJobs;Work.PendingJobs=Update.NewJobs;
|
||||||
Work.StartTime=std::chrono::steady_clock::now();
|
Work.StartTime=CrossingStart;
|
||||||
|
Work.PageCoveredTiles=PageCoveredTiles;Work.CorePageSeconds=PageCompileSeconds;
|
||||||
BuildTargetChunk=Update.CenterChunk;bGenerationRunning=true;
|
BuildTargetChunk=Update.CenterChunk;bGenerationRunning=true;
|
||||||
std::printf("[ChunkCrossing] id=%llu center=(%d,%d,%d) delta=(%d,%d,%d) desired=%d retained=%d new_jobs=%d dropped_resident=%zu cancelled_queued=%d full_detail=%d\n",
|
std::printf("[ChunkCrossing] id=%llu center=(%d,%d,%d) delta=(%d,%d,%d) desired=%d retained=%d new_jobs=%d deferred_resident=%zu cancelled_queued=%d full_detail=%d\n",
|
||||||
static_cast<unsigned long long>(CrossingIndex),Update.CenterChunk.X,Update.CenterChunk.Y,Update.CenterChunk.Z,
|
static_cast<unsigned long long>(CrossingIndex),Update.CenterChunk.X,Update.CenterChunk.Y,Update.CenterChunk.Z,
|
||||||
Update.CenterChunk.X-Previous.X,Update.CenterChunk.Y-Previous.Y,Update.CenterChunk.Z-Previous.Z,
|
Update.CenterChunk.X-Previous.X,Update.CenterChunk.Y-Previous.Y,Update.CenterChunk.Z-Previous.Z,
|
||||||
Update.DesiredTiles,Update.RetainedResidentTiles,Update.NewJobs,Leavers.size(),
|
Update.DesiredTiles,Update.RetainedResidentTiles,Update.NewJobs,
|
||||||
|
bShapeWorld?Leavers.size():0,
|
||||||
Update.CancelledQueuedJobs,Update.FullResTiles);
|
Update.CancelledQueuedJobs,Update.FullResTiles);
|
||||||
std::fflush(stdout);
|
std::fflush(stdout);
|
||||||
ReportCrossingWork();
|
ReportCrossingWork();
|
||||||
@@ -1157,7 +1371,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
{
|
{
|
||||||
if(!Streamer||World->FullResClipLevels<=0)return false;
|
if(!Streamer||World->FullResClipLevels<=0)return false;
|
||||||
int32 ResidentFullRes=0;
|
int32 ResidentFullRes=0;
|
||||||
for(const auto& Pair:ResidentTiles)if(Pair.first.Level<World->FullResClipLevels)++ResidentFullRes;
|
for(const FVoxelTileKey& Key:DesiredTiles)
|
||||||
|
if(Key.Level<World->FullResClipLevels&&ResidentTiles.contains(Key))++ResidentFullRes;
|
||||||
if(ResidentFullRes!=Streamer->FullResDesiredCount())return false;
|
if(ResidentFullRes!=Streamer->FullResDesiredCount())return false;
|
||||||
for(const FStandaloneBatchKey& Key:DirtyBatchSet)if(Key.Level<World->FullResClipLevels)return false;
|
for(const FStandaloneBatchKey& Key:DirtyBatchSet)if(Key.Level<World->FullResClipLevels)return false;
|
||||||
return true;
|
return true;
|
||||||
@@ -1203,14 +1418,16 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
?Work.WorkerSeconds*1000.0/static_cast<double>(Work.FinishedJobs):0.0;
|
?Work.WorkerSeconds*1000.0/static_cast<double>(Work.FinishedJobs):0.0;
|
||||||
const double WallSeconds=Work.StartTime.time_since_epoch().count()!=0
|
const double WallSeconds=Work.StartTime.time_since_epoch().count()!=0
|
||||||
?std::chrono::duration<double>(std::chrono::steady_clock::now()-Work.StartTime).count():0.0;
|
?std::chrono::duration<double>(std::chrono::steady_clock::now()-Work.StartTime).count():0.0;
|
||||||
std::printf("[ChunkCrossingWork] id=%llu submitted=%llu finished=%llu generated=%llu cancelled_queued=%llu stale=%llu samples=%.2fM worker_sum=%.3fs wall=%.3fs avg=%.3fms\n",
|
std::printf("[ChunkCrossingWork] id=%llu submitted=%llu finished=%llu generated=%llu cancelled_queued=%llu stale=%llu samples=%.2fM page_covered_tiles=%llu core_page=%.4fs worker_sum=%.3fs wall=%.3fs avg=%.3fms\n",
|
||||||
static_cast<unsigned long long>(Pair.first),
|
static_cast<unsigned long long>(Pair.first),
|
||||||
static_cast<unsigned long long>(Work.SubmittedJobs),
|
static_cast<unsigned long long>(Work.SubmittedJobs),
|
||||||
static_cast<unsigned long long>(Work.FinishedJobs),
|
static_cast<unsigned long long>(Work.FinishedJobs),
|
||||||
static_cast<unsigned long long>(Work.Tiles),
|
static_cast<unsigned long long>(Work.Tiles),
|
||||||
static_cast<unsigned long long>(Work.CancelledQueuedJobs),
|
static_cast<unsigned long long>(Work.CancelledQueuedJobs),
|
||||||
static_cast<unsigned long long>(Work.StaleResults),
|
static_cast<unsigned long long>(Work.StaleResults),
|
||||||
static_cast<double>(Work.Samples)/1.0e6,Work.WorkerSeconds,WallSeconds,AverageMs);
|
static_cast<double>(Work.Samples)/1.0e6,
|
||||||
|
static_cast<unsigned long long>(Work.PageCoveredTiles),Work.CorePageSeconds,
|
||||||
|
Work.WorkerSeconds,WallSeconds,AverageMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1240,6 +1457,23 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
}
|
}
|
||||||
ReportCrossingWork();
|
ReportCrossingWork();
|
||||||
UploadDirtyBatches();
|
UploadDirtyBatches();
|
||||||
|
if(!DeferredLeaverTiles.empty()&&DirtyBatchSet.empty())
|
||||||
|
{
|
||||||
|
bool bDesiredResident=true;
|
||||||
|
for(const FVoxelTileKey& Key:DesiredTiles)
|
||||||
|
if(!ResidentTiles.contains(Key)){bDesiredResident=false;break;}
|
||||||
|
if(bDesiredResident)
|
||||||
|
{
|
||||||
|
std::vector<FVoxelTileKey> ReadyToDrop;
|
||||||
|
for(const FVoxelTileKey& Key:DeferredLeaverTiles)
|
||||||
|
{
|
||||||
|
if(Streamer->IsDesired(Key))continue;
|
||||||
|
if(ResidentTiles.contains(Key))ReadyToDrop.push_back(Key);
|
||||||
|
}
|
||||||
|
for(const FVoxelTileKey& Key:ReadyToDrop)RemoveResidentTile(Key);
|
||||||
|
DeferredLeaverTiles.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
if(bShapePipelineActive)TryCompleteShapePipeline();
|
if(bShapePipelineActive)TryCompleteShapePipeline();
|
||||||
if(bTestOneChunkCrossing&&bCrossingMoveFinished&&!bCrossingClosePosted
|
if(bTestOneChunkCrossing&&bCrossingMoveFinished&&!bCrossingClosePosted
|
||||||
&&DirtyBatchSet.empty())
|
&&DirtyBatchSet.empty())
|
||||||
@@ -1262,17 +1496,20 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
ReportTileTimings();std::fflush(stdout);
|
ReportTileTimings();std::fflush(stdout);
|
||||||
StartFarShapeGeneration();
|
StartFarShapeGeneration();
|
||||||
}
|
}
|
||||||
if(!bInitialClipmapReported&&ResidentTiles.size()==DesiredTiles.size()&&DirtyBatchSet.empty())
|
size_t DesiredResidentCount=0;
|
||||||
|
for(const FVoxelTileKey& Key:DesiredTiles)
|
||||||
|
if(ResidentTiles.contains(Key))++DesiredResidentCount;
|
||||||
|
if(!bInitialClipmapReported&&DesiredResidentCount==DesiredTiles.size()&&DirtyBatchSet.empty())
|
||||||
{
|
{
|
||||||
bInitialClipmapReported=true;
|
bInitialClipmapReported=true;
|
||||||
const double Elapsed=std::chrono::duration<double>(std::chrono::steady_clock::now()-ColdProcessStart).count();
|
const double Elapsed=std::chrono::duration<double>(std::chrono::steady_clock::now()-ColdProcessStart).count();
|
||||||
std::printf("[InitialClipmapReady] %.3f s after cold process start; %zu tiles, %llu vertices, %llu indices, %zu GPU batches.\n",
|
std::printf("[InitialClipmapReady] %.3f s after cold process start; %zu tiles, %llu vertices, %llu indices, %zu GPU batches.\n",
|
||||||
Elapsed,ResidentTiles.size(),static_cast<unsigned long long>(TotalVertices),
|
Elapsed,DesiredResidentCount,static_cast<unsigned long long>(TotalVertices),
|
||||||
static_cast<unsigned long long>(TotalIndices),StreamingBatches.size());
|
static_cast<unsigned long long>(TotalIndices),StreamingBatches.size());
|
||||||
ReportTileTimings();std::fflush(stdout);
|
ReportTileTimings();std::fflush(stdout);
|
||||||
if(bQuitAfterClipmap&&CapturePath.empty())PostMessageA(Window,WM_CLOSE,0,0);
|
if(bQuitAfterClipmap&&CapturePath.empty())PostMessageA(Window,WM_CLOSE,0,0);
|
||||||
}
|
}
|
||||||
bGenerationRunning=ResidentTiles.size()!=DesiredTiles.size()||!DirtyBatchSet.empty();
|
bGenerationRunning=DesiredResidentCount!=DesiredTiles.size()||!DirtyBatchSet.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CaptureRenderedFrame()
|
void CaptureRenderedFrame()
|
||||||
@@ -2207,6 +2444,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
double FullDetailSeconds=0.0;
|
double FullDetailSeconds=0.0;
|
||||||
const int32 DesiredTotal=Update.DesiredTiles;
|
const int32 DesiredTotal=Update.DesiredTiles;
|
||||||
FMeshSheetAudit MeshSheetStats;
|
FMeshSheetAudit MeshSheetStats;
|
||||||
|
FCoarsePanelAudit CoarsePanelStats;
|
||||||
uint64 MeshAuditedTiles=0;
|
uint64 MeshAuditedTiles=0;
|
||||||
while(Finished<DesiredTotal)
|
while(Finished<DesiredTotal)
|
||||||
{
|
{
|
||||||
@@ -2263,6 +2501,17 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
TileAudit.MinimumThicknessVoxels);
|
TileAudit.MinimumThicknessVoxels);
|
||||||
++MeshAuditedTiles;
|
++MeshAuditedTiles;
|
||||||
}
|
}
|
||||||
|
if(bShardAudit&&Level>=1&&CoreWorld)
|
||||||
|
{
|
||||||
|
const FCoarsePanelAudit TileAudit=AuditCoarseSurfacePanels(
|
||||||
|
Tile,*CoreWorld,CoreWorld->configuration(),CoarsePanelStats.OffSurfacePanels<8);
|
||||||
|
CoarsePanelStats.Tiles+=TileAudit.Tiles;
|
||||||
|
CoarsePanelStats.PlanarOpenComponents+=TileAudit.PlanarOpenComponents;
|
||||||
|
CoarsePanelStats.OffSurfacePanels+=TileAudit.OffSurfacePanels;
|
||||||
|
CoarsePanelStats.Triangles+=TileAudit.Triangles;
|
||||||
|
CoarsePanelStats.MaximumSourceErrorVoxels=std::max(
|
||||||
|
CoarsePanelStats.MaximumSourceErrorVoxels,TileAudit.MaximumSourceErrorVoxels);
|
||||||
|
}
|
||||||
if(!BakeCachePath.empty())CacheTiles.push_back(std::move(Tile));
|
if(!BakeCachePath.empty())CacheTiles.push_back(std::move(Tile));
|
||||||
}
|
}
|
||||||
if(!bFullDetailTimeReported&&FullDetailFinished>=FullDetailExpected)
|
if(!bFullDetailTimeReported&&FullDetailFinished>=FullDetailExpected)
|
||||||
@@ -2350,6 +2599,14 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
static_cast<unsigned long long>(MeshSheetStats.ThinSheets),
|
static_cast<unsigned long long>(MeshSheetStats.ThinSheets),
|
||||||
static_cast<unsigned long long>(MeshSheetStats.TrianglesInThinSheets),
|
static_cast<unsigned long long>(MeshSheetStats.TrianglesInThinSheets),
|
||||||
MeshSheetStats.ThinSheets==0&&MeshSheetStats.SmallComponentsInView==0?"PASS":"FAIL");
|
MeshSheetStats.ThinSheets==0&&MeshSheetStats.SmallComponentsInView==0?"PASS":"FAIL");
|
||||||
|
if(bShardAudit&&CoreWorld)
|
||||||
|
std::printf("[CoarsePanelAudit] tiles=%llu planar_open_components=%llu off_surface_panels=%llu panel_triangles=%llu max_source_error=%.3f vox status=%s\n",
|
||||||
|
static_cast<unsigned long long>(CoarsePanelStats.Tiles),
|
||||||
|
static_cast<unsigned long long>(CoarsePanelStats.PlanarOpenComponents),
|
||||||
|
static_cast<unsigned long long>(CoarsePanelStats.OffSurfacePanels),
|
||||||
|
static_cast<unsigned long long>(CoarsePanelStats.Triangles),
|
||||||
|
CoarsePanelStats.MaximumSourceErrorVoxels,
|
||||||
|
CoarsePanelStats.OffSurfacePanels==0?"PASS":"FAIL");
|
||||||
for(int32 Level=0;Level<=MaxLevels;++Level)
|
for(int32 Level=0;Level<=MaxLevels;++Level)
|
||||||
{
|
{
|
||||||
if(TilesByLevel[Level]==0)continue;
|
if(TilesByLevel[Level]==0)continue;
|
||||||
@@ -2392,6 +2649,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
}
|
}
|
||||||
if(Finished!=DesiredTotal||FullDetailFinished!=FullDetailExpected)return 6;
|
if(Finished!=DesiredTotal||FullDetailFinished!=FullDetailExpected)return 6;
|
||||||
if(bShardAudit&&(MeshSheetStats.ThinSheets!=0||MeshSheetStats.SmallComponentsInView!=0))return 13;
|
if(bShardAudit&&(MeshSheetStats.ThinSheets!=0||MeshSheetStats.SmallComponentsInView!=0))return 13;
|
||||||
|
if(bShardAudit&&CoreWorld&&CoarsePanelStats.OffSurfacePanels!=0)return 13;
|
||||||
if(FarAudit)
|
if(FarAudit)
|
||||||
{
|
{
|
||||||
FarAudit->Finalize();
|
FarAudit->Finalize();
|
||||||
@@ -2483,6 +2741,106 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
AffectedByLevel[Level]==FinishedByLevel[Level]?"PASS":"FAIL");
|
AffectedByLevel[Level]==FinishedByLevel[Level]?"PASS":"FAIL");
|
||||||
return Finished==static_cast<int32>(Affected.size())?0:10;
|
return Finished==static_cast<int32>(Affected.size())?0:10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MakeRegionBuildRequest(std::span<const VoxelForgeCore::TileKey> tiles,
|
||||||
|
const VoxelForgeCore::WorldConfiguration& config,int32 workers,
|
||||||
|
VoxelForgeCore::DetailVariant detail,bool roundTunnels,bool enhancedWalls,
|
||||||
|
VoxelForgeCore::ShapeBuildRequest& request,std::string& error)
|
||||||
|
{
|
||||||
|
FIntVector fineMin(MAX_int32,MAX_int32,MAX_int32),fineMax(MIN_int32,MIN_int32,MIN_int32);
|
||||||
|
FIntVector clipMin(MAX_int32,MAX_int32,MAX_int32),clipMax(MIN_int32,MIN_int32,MIN_int32);
|
||||||
|
for(const auto& key:tiles)
|
||||||
|
{
|
||||||
|
if(key.lod<0||key.lod>8){error="desired tile has an invalid LOD";return false;}
|
||||||
|
const int32 extent=CHUNK_SIZE<<key.lod;
|
||||||
|
const FIntVector origin(
|
||||||
|
static_cast<int32>(static_cast<int64>(key.coordinate.x)*extent),
|
||||||
|
static_cast<int32>(static_cast<int64>(key.coordinate.y)*extent),
|
||||||
|
static_cast<int32>(static_cast<int64>(key.coordinate.z)*extent));
|
||||||
|
for(int32 axis=0;axis<3;++axis)
|
||||||
|
{
|
||||||
|
(&clipMin.X)[axis]=std::min((&clipMin.X)[axis],(&origin.X)[axis]);
|
||||||
|
(&clipMax.X)[axis]=std::max((&clipMax.X)[axis],(&origin.X)[axis]+extent);
|
||||||
|
if(key.lod<config.fullResolutionLodCount)
|
||||||
|
{
|
||||||
|
(&fineMin.X)[axis]=std::min((&fineMin.X)[axis],(&origin.X)[axis]);
|
||||||
|
(&fineMax.X)[axis]=std::max((&fineMax.X)[axis],(&origin.X)[axis]+extent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(fineMin.X==MAX_int32||clipMin.X==MAX_int32)
|
||||||
|
{error="desired region contains no fine tiles or clip bounds";return false;}
|
||||||
|
request.minVoxel={fineMin.X,fineMin.Y,fineMin.Z};
|
||||||
|
request.maxVoxel={fineMax.X,fineMax.Y,fineMax.Z};
|
||||||
|
request.clipMinVoxel={clipMin.X,clipMin.Y,clipMin.Z};
|
||||||
|
request.clipMaxVoxel={clipMax.X,clipMax.Y,clipMax.Z};
|
||||||
|
request.workerLimit=static_cast<uint32>(std::clamp(workers,1,8));
|
||||||
|
request.detail=detail;request.roundTunnels=roundTunnels;
|
||||||
|
request.enhancedTunnelWalls=enhancedWalls;request.includeFarWorld=false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RunTilePathHashAudit(VoxelForgeCore::World& base,
|
||||||
|
const VoxelForgeCore::ResolvedWorldSnapshot& snapshot,
|
||||||
|
const VoxelForgeCore::JobScheduler& scheduler,const FVector& camera,
|
||||||
|
int32 workers,VoxelForgeCore::DetailVariant detail,bool roundTunnels,
|
||||||
|
bool enhancedWalls)
|
||||||
|
{
|
||||||
|
const auto baseTiles=base.desiredTiles({camera.X,camera.Y,camera.Z});
|
||||||
|
const auto& config=base.configuration();
|
||||||
|
if(baseTiles.empty())
|
||||||
|
{std::fprintf(stderr,"[TilePathHashAudit] camera window is empty.\n");return 11;}
|
||||||
|
constexpr int32 TargetChunkOffset=40;
|
||||||
|
const int32 CameraChunkX=FMath::FloorToInt(camera.X/CHUNK_SIZE);
|
||||||
|
const int32 CameraChunkY=FMath::FloorToInt(camera.Y/CHUNK_SIZE);
|
||||||
|
const int32 CameraChunkZ=FMath::FloorToInt(camera.Z/CHUNK_SIZE);
|
||||||
|
const VoxelForgeCore::TileKey target{{CameraChunkX+TargetChunkOffset,
|
||||||
|
CameraChunkY,CameraChunkZ},0};
|
||||||
|
const FVector alternateCamera((target.coordinate.x+0.5)*CHUNK_SIZE,camera.Y,camera.Z);
|
||||||
|
const auto alternateTiles=base.desiredTiles(
|
||||||
|
{alternateCamera.X,alternateCamera.Y,alternateCamera.Z});
|
||||||
|
if(alternateTiles.empty())
|
||||||
|
{std::fprintf(stderr,"[TilePathHashAudit] target camera window is empty.\n");return 11;}
|
||||||
|
|
||||||
|
VoxelForgeCore::TileGenerationOptions options;options.hashDensity=true;
|
||||||
|
std::string error;
|
||||||
|
const std::array<VoxelForgeCore::TileKey,1> pageTiles={target};
|
||||||
|
std::size_t pageTilesA=0;
|
||||||
|
if(!base.compileTileRegion(pageTiles,static_cast<uint32>(std::clamp(workers,1,8)),
|
||||||
|
scheduler,error,&pageTilesA))
|
||||||
|
{std::fprintf(stderr,"[TilePathHashAudit] path-A page compile failed: %s\n",error.c_str());return 11;}
|
||||||
|
auto pathA=base.generateTile(target,options,error);
|
||||||
|
if(!pathA){std::fprintf(stderr,"[TilePathHashAudit] path-A generation failed: %s\n",error.c_str());return 11;}
|
||||||
|
|
||||||
|
VoxelForgeCore::World alternate(snapshot);
|
||||||
|
VoxelForgeCore::ShapeBuildRequest alternateRequest;
|
||||||
|
if(!MakeRegionBuildRequest(alternateTiles,config,workers,detail,roundTunnels,
|
||||||
|
enhancedWalls,alternateRequest,error)
|
||||||
|
||!alternate.compile(alternateRequest,scheduler,error))
|
||||||
|
{std::fprintf(stderr,"[TilePathHashAudit] alternate camera compile failed: %s\n",error.c_str());return 11;}
|
||||||
|
std::size_t pageTilesB=0;
|
||||||
|
auto pathB=alternate.generateTile(target,options,error);
|
||||||
|
if(!pathB){std::fprintf(stderr,"[TilePathHashAudit] path-B generation failed: %s\n",error.c_str());return 11;}
|
||||||
|
const auto Same=[&](const VoxelForgeCore::TileMesh& a,const VoxelForgeCore::TileMesh& b)
|
||||||
|
{
|
||||||
|
return a.densityHash==b.densityHash&&a.meshHash==b.meshHash
|
||||||
|
&&a.payloadHash==b.payloadHash&&a.topologyHash==b.topologyHash;
|
||||||
|
};
|
||||||
|
const int32 CameraDeltaX=static_cast<int32>(std::lround(alternateCamera.X-camera.X));
|
||||||
|
const bool pass=pageTilesA==1&&pageTilesB==0&&Same(*pathA,*pathB);
|
||||||
|
std::printf("[TilePathHashAudit] tile=(%d,%d,%d) lod=%d path_a=late_page path_b=initial_base camera_delta_vox=(%d,0,0) page_covered_tiles=(%zu,%zu) density=(0x%016llx,0x%016llx) mesh=(0x%016llx,0x%016llx) payload=(0x%016llx,0x%016llx) topology=(0x%016llx,0x%016llx) status=%s\n",
|
||||||
|
target.coordinate.x,target.coordinate.y,target.coordinate.z,target.lod,CameraDeltaX,
|
||||||
|
pageTilesA,pageTilesB,
|
||||||
|
static_cast<unsigned long long>(pathA->densityHash),
|
||||||
|
static_cast<unsigned long long>(pathB->densityHash),
|
||||||
|
static_cast<unsigned long long>(pathA->meshHash),
|
||||||
|
static_cast<unsigned long long>(pathB->meshHash),
|
||||||
|
static_cast<unsigned long long>(pathA->payloadHash),
|
||||||
|
static_cast<unsigned long long>(pathB->payloadHash),
|
||||||
|
static_cast<unsigned long long>(pathA->topologyHash),
|
||||||
|
static_cast<unsigned long long>(pathB->topologyHash),pass?"PASS":"FAIL");
|
||||||
|
return pass?0:12;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int Argc,char** Argv)
|
int main(int Argc,char** Argv)
|
||||||
@@ -2500,6 +2858,8 @@ int main(int Argc,char** Argv)
|
|||||||
bool bStreamPipeline=false;
|
bool bStreamPipeline=false;
|
||||||
bool bProfileDetailBand=false;
|
bool bProfileDetailBand=false;
|
||||||
bool bPassageDetailAuditOnly=false;
|
bool bPassageDetailAuditOnly=false;
|
||||||
|
bool bGridStairAudit=false;
|
||||||
|
bool bTilePathHashAudit=false;
|
||||||
bool bShardAudit=false;
|
bool bShardAudit=false;
|
||||||
bool bFarSurvivalAudit=false;
|
bool bFarSurvivalAudit=false;
|
||||||
bool bFarAuditBadShell=false;
|
bool bFarAuditBadShell=false;
|
||||||
@@ -2523,6 +2883,8 @@ int main(int Argc,char** Argv)
|
|||||||
else if(Arg=="--density-hash")App.bDensityHash=true;
|
else if(Arg=="--density-hash")App.bDensityHash=true;
|
||||||
else if(Arg=="--shape-world")App.bShapeWorld=true;
|
else if(Arg=="--shape-world")App.bShapeWorld=true;
|
||||||
else if(Arg=="--shape-audit")App.bShapeWorld=true,App.bShapeAuditOnly=true;
|
else if(Arg=="--shape-audit")App.bShapeWorld=true,App.bShapeAuditOnly=true;
|
||||||
|
else if(Arg=="--grid-stair-audit")bGridStairAudit=true,App.bShapeWorld=true;
|
||||||
|
else if(Arg=="--tile-path-hash-audit")bTilePathHashAudit=true,App.bShapeWorld=true;
|
||||||
else if(Arg=="--passage-wall-detail-audit")
|
else if(Arg=="--passage-wall-detail-audit")
|
||||||
{App.bShapeWorld=true;App.bShapeAuditOnly=true;bPassageDetailAuditOnly=true;}
|
{App.bShapeWorld=true;App.bShapeAuditOnly=true;bPassageDetailAuditOnly=true;}
|
||||||
else if(Arg=="--shape-compare")App.bShapeWorld=true,App.bShapeCompare=true;
|
else if(Arg=="--shape-compare")App.bShapeWorld=true,App.bShapeCompare=true;
|
||||||
@@ -2636,7 +2998,7 @@ int main(int Argc,char** Argv)
|
|||||||
?(StreamBenchmarkMaxLevel<0||StreamBenchmarkMaxLevel>=App.World->FullResClipLevels)
|
?(StreamBenchmarkMaxLevel<0||StreamBenchmarkMaxLevel>=App.World->FullResClipLevels)
|
||||||
:(App.bShapeCompare?ShapeCompareLevel>=App.World->FullResClipLevels
|
:(App.bShapeCompare?ShapeCompareLevel>=App.World->FullResClipLevels
|
||||||
:(BenchmarkLevel>=App.World->FullResClipLevels));
|
:(BenchmarkLevel>=App.World->FullResClipLevels));
|
||||||
if(App.bShapeWorld&&(App.bShapeAuditOnly||App.bShapeCompare||bStreamBenchmark||BenchmarkLevel>=0)
|
if(App.bShapeWorld&&(App.bShapeAuditOnly||bGridStairAudit||bTilePathHashAudit||App.bShapeCompare||bStreamBenchmark||BenchmarkLevel>=0)
|
||||||
&&!bStreamPipeline
|
&&!bStreamPipeline
|
||||||
&&!App.BuildShapeWorldForCamera(Error,bNeedFarShapeWorld))
|
&&!App.BuildShapeWorldForCamera(Error,bNeedFarShapeWorld))
|
||||||
{std::fprintf(stderr,"Shape interval build failed: %s\n",Error.c_str());return 2;}
|
{std::fprintf(stderr,"Shape interval build failed: %s\n",Error.c_str());return 2;}
|
||||||
@@ -2657,6 +3019,12 @@ int main(int Argc,char** Argv)
|
|||||||
App.Yaw,App.Pitch);
|
App.Yaw,App.Pitch);
|
||||||
return App.ShapeWorld->auditDetailPassing()&&bSurfacePassing?0:7;
|
return App.ShapeWorld->auditDetailPassing()&&bSurfacePassing?0:7;
|
||||||
}
|
}
|
||||||
|
if(bGridStairAudit)
|
||||||
|
return App.ShapeWorld->auditGridStairIntegrity()?0:9;
|
||||||
|
if(bTilePathHashAudit)
|
||||||
|
return RunTilePathHashAudit(*App.ShapeWorld,App.Snapshot,App.CoreScheduler,
|
||||||
|
FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0),
|
||||||
|
App.WorkerCount,App.ShapeDetailVariant,App.bRoundTunnels,App.bRoundTunnelEnhanced);
|
||||||
if(App.bShapeCompare)
|
if(App.bShapeCompare)
|
||||||
return RunOwnerShapeComparison(*App.World,*App.ShapeWorld,App.Position,App.Yaw,App.Pitch,App.WorkerCount,
|
return RunOwnerShapeComparison(*App.World,*App.ShapeWorld,App.Position,App.Yaw,App.Pitch,App.WorkerCount,
|
||||||
ShapeCompareLevel,ShapeCompareTileLimit);
|
ShapeCompareLevel,ShapeCompareTileLimit);
|
||||||
|
|||||||
Reference in new issue
Block a user