wip(assembler): round S4 -- continuous far ceiling at the strate's upper boundary
S3's far cap sat up to 8.2 m above the near roof and read as floating pieces. The far render cap now projects onto the owning strate's upper boundary (density unchanged), on a global XY lattice, LOD4 16-voxel grid + LOD5-8 128-voxel. Cap agreement 0.05 cm (100 cm control detected), 2520/2520 upward rays hit roof on the owner walk. Far recipe v2; LOD0-3 hashes equal S2/S3. Tile p95 flat; cold clipmap +4.4%. Far-cliff fluting left open (coarse MC step). 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
d0f97bf79a
commit
9d3c50ca95
14 files changed
+1376
-34
No files matched your search
@@ -22,6 +22,7 @@
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <complex>
|
||||
#include <cstdio>
|
||||
@@ -37,6 +38,7 @@
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
@@ -8311,8 +8313,435 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RunWorldAssemblerCapAudit(FStandaloneOwnerWorld& owner,
|
||||
const VoxelForgeCore::ResolvedWorldSnapshot& snapshot,VoxelForgeCore::World& world,
|
||||
VoxelForgeCore::Vec3d walkCameraVoxels)
|
||||
{
|
||||
// This audit follows the shipped owner manifest and Core clipmap, not the transient BuildHost fixture.
|
||||
// Cet audit suit le manifeste owner livre et le clipmap Core, pas le fixture BuildHost transitoire.
|
||||
constexpr std::int64_t Scale=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
constexpr double Pi=3.14159265358979323846;
|
||||
constexpr double RayStepVoxels=128.0;
|
||||
constexpr double AgreementToleranceCm=2.0;
|
||||
constexpr double NegativeDatumOffsetCm=100.0;
|
||||
using FTileId=std::array<std::int32_t,4>;
|
||||
using FXYLod=std::array<std::int32_t,3>;
|
||||
const double VoxelSizeCm=static_cast<double>(world.configuration().voxelSizeMicrons)/10000.0;
|
||||
const auto IdFor=[](const VoxelForgeCore::TileKey& key)
|
||||
{return FTileId{key.coordinate.x,key.coordinate.y,key.coordinate.z,key.lod};};
|
||||
const auto FloorDiv64=[](std::int64_t value,std::int64_t divisor)
|
||||
{
|
||||
const std::int64_t quotient=value/divisor,remainder=value%divisor;
|
||||
return quotient-((remainder!=0&&remainder<0)?1:0);
|
||||
};
|
||||
const auto* manifest=snapshot.worldAssembly();
|
||||
if(!manifest||manifest->plan.strates.empty())
|
||||
{std::fprintf(stderr,"[WorldAssemblerFarCapAudit] no SurfaceWorld plan; status=FAIL\n");return false;}
|
||||
const auto planIt=std::find_if(manifest->plan.strates.begin(),manifest->plan.strates.end(),
|
||||
[](const auto& plan){return !plan.route.empty();});
|
||||
if(planIt==manifest->plan.strates.end())
|
||||
{std::fprintf(stderr,"[WorldAssemblerFarCapAudit] no authored walk route; status=FAIL\n");return false;}
|
||||
const auto& plan=*planIt;
|
||||
const VoxelForgeCore::Heightfield2D field(manifest->recipe,plan);
|
||||
if(!field.isValid())
|
||||
{std::fprintf(stderr,"[WorldAssemblerFarCapAudit] invalid Heightfield2D; status=FAIL\n");return false;}
|
||||
|
||||
struct FView
|
||||
{
|
||||
VoxelForgeCore::Vec3d Origin{};
|
||||
std::vector<VoxelForgeCore::TileKey> Keys;
|
||||
std::set<FTileId> KeySet;
|
||||
VoxelForgeCore::TileGenerationOptions Options;
|
||||
double ClipMinX=std::numeric_limits<double>::infinity();
|
||||
double ClipMinY=std::numeric_limits<double>::infinity();
|
||||
double ClipMaxX=-std::numeric_limits<double>::infinity();
|
||||
double ClipMaxY=-std::numeric_limits<double>::infinity();
|
||||
double ClipMaxZ=-std::numeric_limits<double>::infinity();
|
||||
};
|
||||
const auto MakeView=[&](VoxelForgeCore::Vec3d origin)
|
||||
{
|
||||
FView view;view.Origin=origin;
|
||||
const FStandaloneDesiredTileSet native=StandaloneWorldMesher::BuildDesiredSet(owner,
|
||||
FVector(origin.x,origin.y,origin.z));
|
||||
view.Options.bandMinZVoxel=native.bHasStrate
|
||||
?native.StrateBottomChunkZ*CHUNK_SIZE:MIN_int32;
|
||||
view.Options.bandMaxZVoxel=native.bHasStrate
|
||||
?(native.StrateTopChunkZ+1)*CHUNK_SIZE:MAX_int32;
|
||||
view.Keys=world.desiredTiles(origin);
|
||||
for(const auto& key:view.Keys)
|
||||
{
|
||||
view.KeySet.insert(IdFor(key));
|
||||
const double extent=static_cast<double>(CHUNK_SIZE<<key.lod);
|
||||
view.ClipMaxZ=std::max(view.ClipMaxZ,(key.coordinate.z+1.0)*extent);
|
||||
if(key.lod<4||key.lod>8)continue;
|
||||
view.ClipMinX=std::min(view.ClipMinX,key.coordinate.x*extent);
|
||||
view.ClipMinY=std::min(view.ClipMinY,key.coordinate.y*extent);
|
||||
view.ClipMaxX=std::max(view.ClipMaxX,(key.coordinate.x+1.0)*extent);
|
||||
view.ClipMaxY=std::max(view.ClipMaxY,(key.coordinate.y+1.0)*extent);
|
||||
}
|
||||
return view;
|
||||
};
|
||||
const FView cameraView=MakeView(walkCameraVoxels);
|
||||
std::set<FXYLod> farXY;
|
||||
for(const auto& key:cameraView.Keys)
|
||||
if(key.lod>=4&&key.lod<=8)farXY.insert({key.coordinate.x,key.coordinate.y,key.lod});
|
||||
std::set<FTileId> capBands;
|
||||
std::uint64_t profileSamples=0,profileSamplesWithoutRoof=0;
|
||||
for(const FXYLod& xy:farXY)
|
||||
{
|
||||
const std::int32_t lod=xy[2];
|
||||
const std::int64_t extent=static_cast<std::int64_t>(CHUNK_SIZE)<<lod;
|
||||
const std::int64_t originX=static_cast<std::int64_t>(xy[0])*extent;
|
||||
const std::int64_t originY=static_cast<std::int64_t>(xy[1])*extent;
|
||||
const std::int64_t gridStep=lod<5?std::min<std::int64_t>(128,1ll<<lod):128;
|
||||
for(std::int64_t y=0;y<=extent;y+=gridStep)
|
||||
for(std::int64_t x=0;x<=extent;x+=gridStep)
|
||||
{
|
||||
++profileSamples;
|
||||
const auto column=field.point((originX+x)*Scale,(originY+y)*Scale,0);
|
||||
const std::int64_t farCap=field.farSurfaceCapHeightSubvoxels(
|
||||
(originX+x)*Scale,(originY+y)*Scale);
|
||||
if(farCap<=column.groundHeightSubvoxels)
|
||||
{++profileSamplesWithoutRoof;continue;}
|
||||
const std::int64_t band=FloorDiv64(farCap,extent*Scale);
|
||||
if(band>=MIN_int32&&band<=MAX_int32)
|
||||
capBands.insert({xy[0],xy[1],static_cast<std::int32_t>(band),lod});
|
||||
if(farCap%(extent*Scale)==0&&band>MIN_int32)
|
||||
capBands.insert({xy[0],xy[1],static_cast<std::int32_t>(band-1),lod});
|
||||
}
|
||||
}
|
||||
|
||||
struct FAgreement {std::int64_t X=0,Y=0;double FarZ=0.0;};
|
||||
struct FGeometry
|
||||
{
|
||||
std::vector<std::array<VoxelForgeCore::Vec3d,3>> DownwardTriangles;
|
||||
std::uint64_t CapTriangles=0;
|
||||
std::vector<FAgreement> AgreementVertices;
|
||||
};
|
||||
constexpr std::int64_t AgreementGrid=128*Scale;
|
||||
std::map<FTileId,FGeometry> geometryCache;
|
||||
std::string coreError;
|
||||
const auto GetGeometry=[&](const FTileId& id,const VoxelForgeCore::TileGenerationOptions& options)
|
||||
->const FGeometry*
|
||||
{
|
||||
if(const auto found=geometryCache.find(id);found!=geometryCache.end())return &found->second;
|
||||
const VoxelForgeCore::TileKey key{{id[0],id[1],id[2]},id[3]};
|
||||
const auto mesh=world.generateTile(key,options,coreError);
|
||||
if(!mesh)return nullptr;
|
||||
FGeometry geometry;
|
||||
geometry.CapTriangles=mesh->diagnostics.horizonCapTriangles;
|
||||
const std::size_t capIndexStart=mesh->indices.size()
|
||||
-static_cast<std::size_t>(geometry.CapTriangles)*3u;
|
||||
for(std::size_t index=0;index+2<mesh->indices.size();index+=3)
|
||||
{
|
||||
const auto ia=mesh->indices[index],ib=mesh->indices[index+1],ic=mesh->indices[index+2];
|
||||
if(ia>=mesh->vertices.size()||ib>=mesh->vertices.size()||ic>=mesh->vertices.size())continue;
|
||||
const auto& a=mesh->vertices[ia];const auto& b=mesh->vertices[ib];const auto& c=mesh->vertices[ic];
|
||||
if((a.normal.z+b.normal.z+c.normal.z)/3.0>=-0.05)continue;
|
||||
geometry.DownwardTriangles.push_back({
|
||||
VoxelForgeCore::Vec3d{a.position.x/VoxelSizeCm,a.position.y/VoxelSizeCm,a.position.z/VoxelSizeCm},
|
||||
VoxelForgeCore::Vec3d{b.position.x/VoxelSizeCm,b.position.y/VoxelSizeCm,b.position.z/VoxelSizeCm},
|
||||
VoxelForgeCore::Vec3d{c.position.x/VoxelSizeCm,c.position.y/VoxelSizeCm,c.position.z/VoxelSizeCm}});
|
||||
}
|
||||
for(std::size_t index=capIndexStart;index<mesh->indices.size();++index)
|
||||
{
|
||||
const std::uint32_t vertexIndex=mesh->indices[index];
|
||||
if(vertexIndex>=mesh->vertices.size())continue;
|
||||
const auto& vertex=mesh->vertices[vertexIndex];
|
||||
if(vertex.normal.z>=-0.35)continue;
|
||||
const std::int64_t x=static_cast<std::int64_t>(std::llround(
|
||||
vertex.position.x/VoxelSizeCm*Scale));
|
||||
const std::int64_t y=static_cast<std::int64_t>(std::llround(
|
||||
vertex.position.y/VoxelSizeCm*Scale));
|
||||
if(x%AgreementGrid!=0||y%AgreementGrid!=0)continue;
|
||||
geometry.AgreementVertices.push_back({x,y,vertex.position.z/VoxelSizeCm});
|
||||
}
|
||||
return &geometryCache.emplace(id,std::move(geometry)).first->second;
|
||||
};
|
||||
|
||||
std::uint64_t requested=0,emitted=0,requestedWithoutTriangles=0,culled=0;
|
||||
std::uint64_t zeroAreaBoundaryTouches=0,unexplainedEmptyBands=0;
|
||||
std::map<std::int32_t,std::array<std::uint64_t,4>> byLod;
|
||||
std::set<std::pair<std::int64_t,std::int64_t>> agreementXY;
|
||||
std::vector<FAgreement> agreementSamples;agreementSamples.reserve(128);
|
||||
for(const FTileId& id:capBands)
|
||||
{
|
||||
auto& count=byLod[id[3]];++count[0];
|
||||
if(!cameraView.KeySet.contains(id)){++culled;++count[1];continue;}
|
||||
++requested;
|
||||
const FGeometry* geometry=GetGeometry(id,cameraView.Options);
|
||||
if(geometry==nullptr)
|
||||
{
|
||||
std::fprintf(stderr,"[WorldAssemblerFarCapAudit] tile generation failed key=(%d,%d,%d,lod%d): %s\n",
|
||||
id[0],id[1],id[2],id[3],coreError.c_str());return false;
|
||||
}
|
||||
if(geometry->CapTriangles==0)
|
||||
{
|
||||
++requestedWithoutTriangles;++count[3];
|
||||
const std::int64_t extent=static_cast<std::int64_t>(VoxelForgeCore::CoreTileVoxelCount)<<id[3];
|
||||
const std::int64_t step=id[3]<5?std::min<std::int64_t>(128,1ll<<id[3]):128;
|
||||
const std::int64_t minZ=static_cast<std::int64_t>(id[2])*extent*Scale;
|
||||
const std::int64_t maxZ=minZ+extent*Scale;
|
||||
const std::int64_t originX=static_cast<std::int64_t>(id[0])*extent;
|
||||
const std::int64_t originY=static_cast<std::int64_t>(id[1])*extent;
|
||||
std::uint64_t validSamples=0,boundarySamples=0,interiorSamples=0;
|
||||
for(std::int64_t y=0;y<=extent;y+=step)
|
||||
for(std::int64_t x=0;x<=extent;x+=step)
|
||||
{
|
||||
const auto column=field.point((originX+x)*Scale,(originY+y)*Scale,0);
|
||||
const std::int64_t farCap=field.farSurfaceCapHeightSubvoxels(
|
||||
(originX+x)*Scale,(originY+y)*Scale);
|
||||
if(farCap<=column.groundHeightSubvoxels||farCap<minZ||farCap>maxZ)continue;
|
||||
++validSamples;
|
||||
if(farCap==minZ||farCap==maxZ)++boundarySamples;
|
||||
else ++interiorSamples;
|
||||
}
|
||||
const bool zeroAreaTouch=validSamples>0&&boundarySamples==validSamples
|
||||
&&interiorSamples==0;
|
||||
if(zeroAreaTouch)++zeroAreaBoundaryTouches;else ++unexplainedEmptyBands;
|
||||
std::printf("[WorldAssemblerFarCapEmptyBand] key=(%d,%d,%d,lod%d) valid_profile_samples=%llu boundary_samples=%llu interior_samples=%llu classification=%s\n",
|
||||
id[0],id[1],id[2],id[3],static_cast<unsigned long long>(validSamples),
|
||||
static_cast<unsigned long long>(boundarySamples),static_cast<unsigned long long>(interiorSamples),
|
||||
zeroAreaTouch?"zero_area_boundary_touch":"UNEXPLAINED");
|
||||
continue;
|
||||
}
|
||||
++emitted;++count[2];
|
||||
for(const FAgreement& sample:geometry->AgreementVertices)
|
||||
{
|
||||
if(agreementSamples.size()>=128)break;
|
||||
if(!agreementXY.insert({sample.X,sample.Y}).second)continue;
|
||||
agreementSamples.push_back(sample);
|
||||
}
|
||||
}
|
||||
std::uint64_t agreementCompared=0;
|
||||
double maximumAgreementCm=0.0,maximumNegativeControlCm=0.0;
|
||||
FAgreement maximumAgreementSample{};double maximumAgreementNearRoot=0.0;
|
||||
for(const FAgreement& sample:agreementSamples)
|
||||
{
|
||||
const auto column=field.point(sample.X,sample.Y,0);
|
||||
const std::int64_t farCap=field.farSurfaceCapHeightSubvoxels(sample.X,sample.Y);
|
||||
if(farCap<=column.groundHeightSubvoxels)continue;
|
||||
const double x=static_cast<double>(sample.X)/Scale,y=static_cast<double>(sample.Y)/Scale;
|
||||
const double capVoxel=static_cast<double>(farCap)/Scale;
|
||||
bool foundRoot=false;double nearRoot=0.0;
|
||||
for(double radius=4.0;radius<=4096.0;radius*=2.0)
|
||||
{
|
||||
double lo=capVoxel-radius,hi=capVoxel+radius;
|
||||
if(!(world.densityAt({x,y,lo})>=0.0f&&world.densityAt({x,y,hi})<0.0f))continue;
|
||||
for(int iteration=0;iteration<16;++iteration)
|
||||
{const double mid=(lo+hi)*0.5;if(world.densityAt({x,y,mid})>=0.0f)lo=mid;else hi=mid;}
|
||||
nearRoot=(lo+hi)*0.5;foundRoot=true;break;
|
||||
}
|
||||
if(!foundRoot)continue;
|
||||
const double sampleErrorCm=std::abs(sample.FarZ-nearRoot)*VoxelSizeCm;
|
||||
if(sampleErrorCm>maximumAgreementCm)
|
||||
{maximumAgreementCm=sampleErrorCm;maximumAgreementSample=sample;maximumAgreementNearRoot=nearRoot;}
|
||||
maximumNegativeControlCm=std::max(maximumNegativeControlCm,
|
||||
std::abs(sample.FarZ+NegativeDatumOffsetCm/VoxelSizeCm-nearRoot)*VoxelSizeCm);
|
||||
++agreementCompared;
|
||||
}
|
||||
if(agreementCompared>0)
|
||||
{
|
||||
const double capVoxel=static_cast<double>(field.farSurfaceCapHeightSubvoxels(
|
||||
maximumAgreementSample.X,maximumAgreementSample.Y))/Scale;
|
||||
const double x=static_cast<double>(maximumAgreementSample.X)/Scale;
|
||||
const double y=static_cast<double>(maximumAgreementSample.Y)/Scale;
|
||||
std::printf("[WorldAssemblerFarCapAgreementMax] xy_vox=(%.1f,%.1f) profile_cap_vox=%.4f far_cap_vox=%.4f near_root_vox=%.4f density_at_cap=%.4f density_below=%.4f density_above=%.4f\n",
|
||||
x,y,capVoxel,maximumAgreementSample.FarZ,maximumAgreementNearRoot,
|
||||
world.densityAt({x,y,capVoxel}),world.densityAt({x,y,capVoxel-4.0}),
|
||||
world.densityAt({x,y,capVoxel+4.0}));
|
||||
}
|
||||
const bool negativeControlDetected=agreementCompared>0
|
||||
&&maximumNegativeControlCm>AgreementToleranceCm;
|
||||
std::printf("[WorldAssemblerFarCapCensus] view=owner_camera far_xy_tiles=%zu profile_samples=%llu samples_without_roof=%llu cap_bands=%zu requested=%llu emitted=%llu requested_without_triangles=%llu zero_area_boundary_touches=%llu unexplained_empty_bands=%llu culled_by_clipmap=%llu\n",
|
||||
farXY.size(),static_cast<unsigned long long>(profileSamples),
|
||||
static_cast<unsigned long long>(profileSamplesWithoutRoof),capBands.size(),
|
||||
static_cast<unsigned long long>(requested),static_cast<unsigned long long>(emitted),
|
||||
static_cast<unsigned long long>(requestedWithoutTriangles),
|
||||
static_cast<unsigned long long>(zeroAreaBoundaryTouches),
|
||||
static_cast<unsigned long long>(unexplainedEmptyBands),static_cast<unsigned long long>(culled));
|
||||
for(const auto& [lod,count]:byLod)
|
||||
std::printf("[WorldAssemblerFarCapLod] lod=%d cap_bands=%llu culled=%llu emitted=%llu requested_without_triangles=%llu\n",
|
||||
lod,static_cast<unsigned long long>(count[0]),static_cast<unsigned long long>(count[1]),
|
||||
static_cast<unsigned long long>(count[2]),static_cast<unsigned long long>(count[3]));
|
||||
std::printf("[WorldAssemblerFarCapAgreement] status=%s shared_points=%llu max_far_near_cm=%.4f tolerance_cm=%.2f negative_control=100cm_datum_offset max_error_cm=%.4f detected=%d\n",
|
||||
agreementCompared>=32&&maximumAgreementCm<=AgreementToleranceCm&&negativeControlDetected?"PASS":"FAIL",
|
||||
static_cast<unsigned long long>(agreementCompared),maximumAgreementCm,AgreementToleranceCm,
|
||||
maximumNegativeControlCm,negativeControlDetected?1:0);
|
||||
|
||||
constexpr std::array<double,8> azimuths={0.0,45.0,90.0,135.0,180.0,225.0,270.0,315.0};
|
||||
constexpr std::array<double,9> elevations={5.0,10.0,12.0,15.0,30.0,45.0,60.0,75.0,90.0};
|
||||
const auto dot3=[](const VoxelForgeCore::Vec3d& a,const VoxelForgeCore::Vec3d& b)
|
||||
{return a.x*b.x+a.y*b.y+a.z*b.z;};
|
||||
const auto sub3=[](const VoxelForgeCore::Vec3d& a,const VoxelForgeCore::Vec3d& b)
|
||||
{return VoxelForgeCore::Vec3d{a.x-b.x,a.y-b.y,a.z-b.z};};
|
||||
const auto cross3=[](const VoxelForgeCore::Vec3d& a,const VoxelForgeCore::Vec3d& b)
|
||||
{return VoxelForgeCore::Vec3d{a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x};};
|
||||
const auto rayTriangle=[&](const VoxelForgeCore::Vec3d& origin,const VoxelForgeCore::Vec3d& direction,
|
||||
const std::array<VoxelForgeCore::Vec3d,3>& triangle,double minDistance,double maxDistance)
|
||||
{
|
||||
const auto edge1=sub3(triangle[1],triangle[0]),edge2=sub3(triangle[2],triangle[0]);
|
||||
const auto p=cross3(direction,edge2);const double determinant=dot3(edge1,p);
|
||||
if(std::abs(determinant)<1.0e-12)return false;
|
||||
const double inverse=1.0/determinant;const auto t=sub3(origin,triangle[0]);
|
||||
const double u=dot3(t,p)*inverse;if(u< -1.0e-8||u>1.0+1.0e-8)return false;
|
||||
const auto q=cross3(t,edge1);const double v=dot3(direction,q)*inverse;
|
||||
if(v< -1.0e-8||u+v>1.0+1.0e-8)return false;
|
||||
const double distance=dot3(edge2,q)*inverse;
|
||||
return distance>=minDistance-1.0e-6&&distance<=maxDistance+1.0e-6;
|
||||
};
|
||||
|
||||
std::vector<VoxelForgeCore::Vec3d> walkOrigins;
|
||||
walkOrigins.reserve(plan.route.size()+1);
|
||||
for(const auto& point:plan.route)
|
||||
walkOrigins.push_back({static_cast<double>(point.positionSubvoxels[0])/Scale,
|
||||
static_cast<double>(point.positionSubvoxels[1])/Scale,
|
||||
static_cast<double>(point.positionSubvoxels[2])/Scale});
|
||||
const auto sameCamera=std::find_if(walkOrigins.begin(),walkOrigins.end(),[&](const auto& point)
|
||||
{return std::abs(point.x-walkCameraVoxels.x)<1.0&&std::abs(point.y-walkCameraVoxels.y)<1.0
|
||||
&&std::abs(point.z-walkCameraVoxels.z)<1.0;});
|
||||
if(sameCamera==walkOrigins.end())walkOrigins.push_back(walkCameraVoxels);
|
||||
const VoxelForgeCore::Vec3d farStraightUpCamera{0.0,-1800.0*4.0,walkCameraVoxels.z};
|
||||
const std::size_t farStraightUpIndex=walkOrigins.size();
|
||||
walkOrigins.push_back(farStraightUpCamera);
|
||||
std::uint64_t rayCount=0,rayHits=0,profileCrossings=0,profileRoofCrossings=0;
|
||||
std::uint64_t missesNoProfileRoof=0,missesMesh=0,missesClipEdge=0;
|
||||
std::array<std::uint64_t,9> raysByElevation{},hitsByElevation{};
|
||||
std::vector<std::string> firstMisses;firstMisses.reserve(8);
|
||||
for(std::size_t viewIndex=0;viewIndex<walkOrigins.size();++viewIndex)
|
||||
{
|
||||
const std::uint64_t hitsBeforeView=rayHits;
|
||||
const std::uint64_t raysBeforeView=rayCount;
|
||||
const auto view=MakeView(walkOrigins[viewIndex]);
|
||||
if(view.ClipMinX>view.ClipMaxX||view.ClipMinY>view.ClipMaxY)continue;
|
||||
const auto startColumn=field.point(
|
||||
static_cast<std::int64_t>(std::llround(view.Origin.x*Scale)),
|
||||
static_cast<std::int64_t>(std::llround(view.Origin.y*Scale)),0);
|
||||
const double startCap=static_cast<double>(field.farSurfaceCapHeightSubvoxels(
|
||||
static_cast<std::int64_t>(std::llround(view.Origin.x*Scale)),
|
||||
static_cast<std::int64_t>(std::llround(view.Origin.y*Scale))))/Scale;
|
||||
for(std::size_t elevationIndex=0;elevationIndex<elevations.size();++elevationIndex)
|
||||
for(const double azimuth:azimuths)
|
||||
{
|
||||
++rayCount;++raysByElevation[elevationIndex];
|
||||
const double elevation=elevations[elevationIndex]*Pi/180.0;
|
||||
const double radians=azimuth*Pi/180.0;
|
||||
const VoxelForgeCore::Vec3d direction{
|
||||
std::cos(elevation)*std::cos(radians),std::cos(elevation)*std::sin(radians),std::sin(elevation)};
|
||||
double edgeDistance=std::numeric_limits<double>::infinity();
|
||||
if(direction.x>1.0e-9)edgeDistance=std::min(edgeDistance,(view.ClipMaxX-view.Origin.x)/direction.x);
|
||||
else if(direction.x< -1.0e-9)edgeDistance=std::min(edgeDistance,(view.ClipMinX-view.Origin.x)/direction.x);
|
||||
if(direction.y>1.0e-9)edgeDistance=std::min(edgeDistance,(view.ClipMaxY-view.Origin.y)/direction.y);
|
||||
else if(direction.y< -1.0e-9)edgeDistance=std::min(edgeDistance,(view.ClipMinY-view.Origin.y)/direction.y);
|
||||
if(direction.z>1.0e-9)edgeDistance=std::min(edgeDistance,(view.ClipMaxZ-view.Origin.z)/direction.z);
|
||||
if(!std::isfinite(edgeDistance)||edgeDistance<=0.0)
|
||||
{++missesClipEdge;continue;}
|
||||
bool profileCrossing=false,profileRoof=false;
|
||||
double previousDistance=0.0,previousDelta=view.Origin.z-startCap;
|
||||
for(double distance=std::min(RayStepVoxels,edgeDistance);;)
|
||||
{
|
||||
const auto column=field.point(
|
||||
static_cast<std::int64_t>(std::llround((view.Origin.x+direction.x*distance)*Scale)),
|
||||
static_cast<std::int64_t>(std::llround((view.Origin.y+direction.y*distance)*Scale)),0);
|
||||
const double cap=static_cast<double>(field.farSurfaceCapHeightSubvoxels(
|
||||
static_cast<std::int64_t>(std::llround((view.Origin.x+direction.x*distance)*Scale)),
|
||||
static_cast<std::int64_t>(std::llround((view.Origin.y+direction.y*distance)*Scale))))/Scale;
|
||||
const double delta=view.Origin.z+direction.z*distance-cap;
|
||||
if(previousDelta<0.0&&delta>=0.0)
|
||||
{
|
||||
const double fraction=-previousDelta/(delta-previousDelta);
|
||||
const double hitX=view.Origin.x+direction.x*(previousDistance+(distance-previousDistance)*fraction);
|
||||
const double hitY=view.Origin.y+direction.y*(previousDistance+(distance-previousDistance)*fraction);
|
||||
const auto hitColumn=field.point(static_cast<std::int64_t>(std::llround(hitX*Scale)),
|
||||
static_cast<std::int64_t>(std::llround(hitY*Scale)),0);
|
||||
profileCrossing=true;++profileCrossings;
|
||||
profileRoof=field.farSurfaceCapHeightSubvoxels(
|
||||
static_cast<std::int64_t>(std::llround(hitX*Scale)),
|
||||
static_cast<std::int64_t>(std::llround(hitY*Scale)))
|
||||
>hitColumn.groundHeightSubvoxels;
|
||||
if(profileRoof)++profileRoofCrossings;
|
||||
break;
|
||||
}
|
||||
previousDistance=distance;previousDelta=delta;
|
||||
if(distance>=edgeDistance)break;
|
||||
distance=std::min(distance+RayStepVoxels,edgeDistance);
|
||||
}
|
||||
bool meshHit=false,intersectedRequestedTile=false,intersectedEmptyTile=false;
|
||||
const auto& rayOrigin=view.Origin;
|
||||
for(const auto& key:view.Keys)
|
||||
{
|
||||
const double extent=static_cast<double>(CHUNK_SIZE<<key.lod);
|
||||
const double minBounds[3]={key.coordinate.x*extent,key.coordinate.y*extent,key.coordinate.z*extent};
|
||||
const double maxBounds[3]={minBounds[0]+extent,minBounds[1]+extent,minBounds[2]+extent};
|
||||
const double origins[3]={rayOrigin.x,rayOrigin.y,rayOrigin.z};
|
||||
const double directions[3]={direction.x,direction.y,direction.z};
|
||||
double enter=0.0,exit=edgeDistance;bool intersects=true;
|
||||
for(int axis=0;axis<3;++axis)
|
||||
{
|
||||
if(std::abs(directions[axis])<1.0e-12)
|
||||
{if(origins[axis]<minBounds[axis]||origins[axis]>maxBounds[axis]){intersects=false;break;}continue;}
|
||||
double tNear=(minBounds[axis]-origins[axis])/directions[axis];
|
||||
double tFar=(maxBounds[axis]-origins[axis])/directions[axis];
|
||||
if(tNear>tFar)std::swap(tNear,tFar);
|
||||
enter=std::max(enter,tNear);exit=std::min(exit,tFar);
|
||||
if(enter>exit){intersects=false;break;}
|
||||
}
|
||||
if(!intersects||exit<0.0||enter>edgeDistance)continue;
|
||||
intersectedRequestedTile=true;
|
||||
const FGeometry* geometry=GetGeometry(IdFor(key),view.Options);
|
||||
if(geometry==nullptr)
|
||||
{std::fprintf(stderr,"[WorldAssemblerFarCapAudit] ray tile generation failed: %s\n",coreError.c_str());return false;}
|
||||
if(geometry->DownwardTriangles.empty()){intersectedEmptyTile=true;continue;}
|
||||
for(const auto& triangle:geometry->DownwardTriangles)
|
||||
if(rayTriangle(rayOrigin,direction,triangle,std::max(0.0,enter),std::min(edgeDistance,exit)))
|
||||
{meshHit=true;break;}
|
||||
if(meshHit)break;
|
||||
}
|
||||
if(meshHit){++rayHits;++hitsByElevation[elevationIndex];continue;}
|
||||
if(!profileCrossing||!profileRoof)++missesNoProfileRoof;
|
||||
else ++missesMesh;
|
||||
if(firstMisses.size()<8)
|
||||
{
|
||||
std::ostringstream message;
|
||||
message<<"view="<<viewIndex<<" elevation="<<elevations[elevationIndex]
|
||||
<<" azimuth="<<azimuth<<" profile_crossing="<<(profileCrossing?1:0)
|
||||
<<" profile_roof="<<(profileRoof?1:0)<<" requested_tile="<<(intersectedRequestedTile?1:0)
|
||||
<<" empty_tile="<<(intersectedEmptyTile?1:0);
|
||||
firstMisses.push_back(message.str());
|
||||
}
|
||||
}
|
||||
const char* viewLabel=viewIndex<plan.route.size()?"route_point":
|
||||
(viewIndex==farStraightUpIndex?"far_straight_up":"owner_camera");
|
||||
std::printf("[WorldAssemblerFarCapCoverageView] view=%s index=%zu origin_vox=(%.1f,%.1f,%.1f) rays=%llu hits=%llu coverage_pct=%.3f\n",
|
||||
viewLabel,viewIndex,walkOrigins[viewIndex].x,walkOrigins[viewIndex].y,
|
||||
walkOrigins[viewIndex].z,static_cast<unsigned long long>(rayCount-raysBeforeView),
|
||||
static_cast<unsigned long long>(rayHits-hitsBeforeView),
|
||||
rayCount>raysBeforeView?100.0*static_cast<double>(rayHits-hitsBeforeView)/(rayCount-raysBeforeView):0.0);
|
||||
}
|
||||
std::printf("[WorldAssemblerFarCapCoverage] rays=%llu hits=%llu coverage_pct=%.3f profile_crossings=%llu profile_roof_crossings=%llu misses_no_profile_roof=%llu misses_mesh=%llu misses_clip_edge=%llu view_points=%zu\n",
|
||||
static_cast<unsigned long long>(rayCount),static_cast<unsigned long long>(rayHits),
|
||||
rayCount?100.0*static_cast<double>(rayHits)/rayCount:0.0,
|
||||
static_cast<unsigned long long>(profileCrossings),static_cast<unsigned long long>(profileRoofCrossings),
|
||||
static_cast<unsigned long long>(missesNoProfileRoof),static_cast<unsigned long long>(missesMesh),
|
||||
static_cast<unsigned long long>(missesClipEdge),walkOrigins.size());
|
||||
for(std::size_t i=0;i<elevations.size();++i)
|
||||
std::printf("[WorldAssemblerFarCapCoverageElevation] elevation_deg=%.0f rays=%llu hits=%llu\n",
|
||||
elevations[i],static_cast<unsigned long long>(raysByElevation[i]),
|
||||
static_cast<unsigned long long>(hitsByElevation[i]));
|
||||
for(const auto& miss:firstMisses)std::printf("[WorldAssemblerFarCapCoverageMiss] %s\n",miss.c_str());
|
||||
const bool censusPassing=requested>0&&unexplainedEmptyBands==0&&culled==0;
|
||||
const bool agreementPassing=agreementCompared>=32&&maximumAgreementCm<=AgreementToleranceCm
|
||||
&&negativeControlDetected;
|
||||
const bool coveragePassing=rayCount>0&&rayHits==rayCount;
|
||||
std::printf("[WorldAssemblerFarCapAudit] census=%s agreement=%s coverage=%s status=%s\n",
|
||||
censusPassing?"PASS":"FAIL",agreementPassing?"PASS":"FAIL",
|
||||
coveragePassing?"PASS":"FAIL",censusPassing&&agreementPassing&&coveragePassing?"PASS":"FAIL");
|
||||
return censusPassing&&agreementPassing&&coveragePassing;
|
||||
}
|
||||
|
||||
int RunWorldAssemblerAudit(FStandaloneOwnerWorld& owner,
|
||||
const VoxelForgeCore::ResolvedWorldSnapshot& snapshot,const std::string& captureDirectory)
|
||||
const VoxelForgeCore::ResolvedWorldSnapshot& snapshot,const std::string& captureDirectory,
|
||||
VoxelForgeCore::World* compiledWorld,VoxelForgeCore::Vec3d walkCameraVoxels)
|
||||
{
|
||||
const auto* manifest=snapshot.worldAssembly();
|
||||
if(!manifest||manifest->plan.strates.empty())
|
||||
@@ -8858,9 +9287,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
std::printf("[WorldAssemblerReadiness] local_field_query=%s analytic_collision_validation=%s physics_collision_cooking=deferred_to_Unreal_adapter render_mesh_readiness=reported_by_matched_view_runs\n",
|
||||
coreQueryPass?"ready_without_render_meshes":"not_ready",
|
||||
routeAudit.passed?"route_sweep_ready":"not_ready");
|
||||
const bool farCapAuditPassing=compiledWorld!=nullptr
|
||||
&&RunWorldAssemblerCapAudit(owner,snapshot,*compiledWorld,walkCameraVoxels);
|
||||
return bounds.passed&&shelfThickness.passed&&routeAudit.passed
|
||||
&&routeSupportNegativeControl&&cliffRegressionPassing&&seamClassificationPassing
|
||||
&&deterministic&&coreQueryPass?0:11;
|
||||
&&deterministic&&coreQueryPass&&farCapAuditPassing?0:11;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9254,7 +9685,11 @@ int main(int Argc,char** Argv)
|
||||
App.bRoundTunnelEnhanced,Error);
|
||||
if(!App.Snapshot)
|
||||
{std::fprintf(stderr,"World assembler snapshot setup failed: %s\n",Error.c_str());return 2;}
|
||||
return RunWorldAssemblerAudit(*App.World,App.Snapshot,WorldAssemblyCaptureDir);
|
||||
App.bShapeWorld=true;
|
||||
if(!App.BuildShapeWorldForCamera(Error,true,false,true))
|
||||
{std::fprintf(stderr,"World assembler cap audit shape build failed: %s\n",Error.c_str());return 2;}
|
||||
return RunWorldAssemblerAudit(*App.World,App.Snapshot,WorldAssemblyCaptureDir,
|
||||
App.ShapeWorld.get(),{App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0});
|
||||
}
|
||||
const bool bNeedFarShapeWorld=bStreamBenchmark
|
||||
?(StreamBenchmarkMaxLevel<0||StreamBenchmarkMaxLevel>=App.World->FullResClipLevels)
|
||||
|
||||
Reference in new issue
Block a user