passages: continuous floor h(s) along the dense route; shell-probe oracle fix (L115, v223)
Floor height from the planner profile h(s) by horizontal projection + arc-length interpolation; adjacent fine segments share endpoint values. Floor zero-crossing offsets max 7.8 mm (L114: up to 73 mm); rock-floor close-ups show no ribs. The 2 L114 shell misses were oracle false positives (points inside an overlapping bore); the oracle now checks every overlapping span; no gate relaxed. Fast tier PASS; Clang/GCC W1/W8/W12 deterministic. Speed regression grows: near-ready +17..+31% vs L109 (fine-segment/sample pairs 49.0M vs 19.8M). Not merged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
5e875ae504
commit
f403af1fc1
37 files changed
+2741
-183
No files matched your search
@@ -3166,18 +3166,58 @@ namespace StandaloneStreamBenchmark
|
||||
std::lerp(a.floorZ,b.floorZ,t),
|
||||
std::lerp(a.radius,b.radius,t),distance,b.segmentIndex};
|
||||
};
|
||||
const char* l114DensityProfile=std::getenv("VOXELFORGE_L114_DENSITY_PROFILE");
|
||||
if(l114DensityProfile&&l114DensityProfile[0]=='1'&&l114DensityProfile[1]=='\0')
|
||||
struct FPassageRouteProjection
|
||||
{
|
||||
for(const char* passageId:{"passage/0-1/0","passage/1-2/0"})
|
||||
double centerDistanceSquared=std::numeric_limits<double>::infinity();
|
||||
double tubeDistance=std::numeric_limits<double>::infinity();
|
||||
double centerAlong=0.0,tubeAlong=0.0;
|
||||
};
|
||||
const auto projectPassageRoute=[](
|
||||
const std::vector<FPassageAuditRoutePoint>& route,
|
||||
double x,double y,double z)
|
||||
{
|
||||
FPassageRouteProjection projection;
|
||||
for(std::size_t segment=1;segment<route.size();++segment)
|
||||
{
|
||||
const auto passage=std::find_if(ResolvedLayout->passages.begin(),
|
||||
ResolvedLayout->passages.end(),[&](const auto& candidate)
|
||||
{return candidate.stableId==passageId;});
|
||||
if(passage==ResolvedLayout->passages.end())continue;
|
||||
const std::size_t passageIndex=static_cast<std::size_t>(
|
||||
passage-ResolvedLayout->passages.begin());
|
||||
const auto& a=route[segment-1];
|
||||
const auto& b=route[segment];
|
||||
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
|
||||
const double lengthSquared=dx*dx+dy*dy+dz*dz;
|
||||
if(lengthSquared<=1.0e-12)continue;
|
||||
const double horizontalLengthSquared=dx*dx+dy*dy;
|
||||
const double horizontalT=horizontalLengthSquared>1.0e-12
|
||||
?((x-a.x)*dx+(y-a.y)*dy)/horizontalLengthSquared
|
||||
:std::clamp((z-a.z)*dz/lengthSquared,0.0,1.0);
|
||||
const double t=std::clamp(((x-a.x)*dx+(y-a.y)*dy
|
||||
+(z-a.z)*dz)/lengthSquared,0.0,1.0);
|
||||
const double ex=x-(a.x+t*dx),ey=y-(a.y+t*dy),ez=z-(a.z+t*dz);
|
||||
const double distanceSquared=ex*ex+ey*ey+ez*ez;
|
||||
const double along=std::lerp(a.distance,b.distance,t);
|
||||
const double centerAlong=std::lerp(a.distance,b.distance,horizontalT);
|
||||
if(distanceSquared<projection.centerDistanceSquared)
|
||||
{
|
||||
projection.centerDistanceSquared=distanceSquared;
|
||||
projection.centerAlong=centerAlong;
|
||||
}
|
||||
const double radius=std::lerp(a.radius,b.radius,t);
|
||||
const double roundDistance=std::sqrt(distanceSquared)-radius;
|
||||
if(roundDistance<projection.tubeDistance)
|
||||
{
|
||||
projection.tubeDistance=roundDistance;
|
||||
projection.tubeAlong=along;
|
||||
}
|
||||
}
|
||||
return projection;
|
||||
};
|
||||
const char* l115DensityProfile=std::getenv("VOXELFORGE_L115_DENSITY_PROFILE");
|
||||
if(l115DensityProfile&&l115DensityProfile[0]=='1'&&l115DensityProfile[1]=='\0')
|
||||
{
|
||||
for(std::size_t passageIndex=0;
|
||||
passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||
{
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
const auto& route=allPassageSmoothRoutes[passageIndex];
|
||||
if(route.size()<2)continue;
|
||||
const auto densityMetres=[&](double x,double y,double z)
|
||||
{
|
||||
return static_cast<double>(Shape.densityAt({x/coreMetres,
|
||||
@@ -3198,34 +3238,38 @@ namespace StandaloneStreamBenchmark
|
||||
}
|
||||
return (low+high)*0.5;
|
||||
};
|
||||
const std::array<std::pair<const char*,double>,2> targets={{
|
||||
{"rock-station-50",route.empty()?0.0:route.back().distance*0.5},
|
||||
{"open-air-section-3",1265.3425}}};
|
||||
for(const auto& [label,targetDistance]:targets)
|
||||
constexpr std::array<const char*,3> labels{{
|
||||
"route-station-25","route-station-50","route-station-75"}};
|
||||
for(std::size_t targetIndex=0;targetIndex<labels.size();++targetIndex)
|
||||
{
|
||||
if(route.size()<2||targetDistance>route.back().distance)continue;
|
||||
const double targetDistance=route.back().distance
|
||||
*static_cast<double>(targetIndex+1)/4.0;
|
||||
const char* label=labels[targetIndex];
|
||||
const auto center=interpolatePassageRoute(route,targetDistance);
|
||||
const VoxelForgeCore::Vec3d centerVoxel={center.x/coreMetres,
|
||||
center.y/coreMetres,center.floorZ/coreMetres};
|
||||
std::printf("[L114DensityColumn] passage=%s section=%s x_m=%.6f y_m=%.6f planned_floor_m=%.6f dispatch=%s source_density_m=%.9f unclamped_density_m=%.9f\n",
|
||||
passage->stableId.c_str(),label,center.x,center.y,center.floorZ,
|
||||
std::printf("[L115DensityColumn] passage=%s station=%s route_s_m=%.3f route_length_m=%.3f x_m=%.6f y_m=%.6f planned_floor_m=%.6f dispatch=%s source_density_m=%.9f unclamped_density_m=%.9f\n",
|
||||
passage.stableId.c_str(),label,targetDistance,
|
||||
route.back().distance,center.x,center.y,center.floorZ,
|
||||
Shape.densityDispatchPathAt(centerVoxel).c_str(),
|
||||
static_cast<double>(Shape.passageSourceDensityAt(centerVoxel))*coreMetres,
|
||||
static_cast<double>(Shape.unclampedDensityAt(centerVoxel))*coreMetres);
|
||||
for(int sample=0;sample<=40;++sample)
|
||||
{
|
||||
const double offset=-0.50+sample*0.025;
|
||||
std::printf("[L114DensityColumnSample] section=%s dz_m=%.3f density_m=%.9f\n",
|
||||
label,offset,densityMetres(center.x,center.y,
|
||||
std::printf("[L115DensityColumnSample] passage=%s station=%s dz_m=%.3f density_m=%.9f\n",
|
||||
passage.stableId.c_str(),label,offset,densityMetres(center.x,center.y,
|
||||
center.floorZ+offset));
|
||||
}
|
||||
for(int sample=-25;sample<=25;++sample)
|
||||
{
|
||||
const double distance=targetDistance+sample*0.10;
|
||||
const double distance=std::clamp(targetDistance
|
||||
+sample*0.10,0.0,route.back().distance);
|
||||
const auto pose=interpolatePassageRoute(route,distance);
|
||||
const double root=floorRoot(pose);
|
||||
std::printf("[L114DensitySlopeSample] section=%s s_m=%.3f x_m=%.6f y_m=%.6f planned_floor_m=%.6f root_floor_m=%.6f residual_m=%.6f density_minus_0p1_m=%.9f density_plane_m=%.9f density_plus_0p1_m=%.9f\n",
|
||||
label,distance,pose.x,pose.y,pose.floorZ,root,
|
||||
std::printf("[L115DensitySlopeSample] passage=%s station=%s s_m=%.3f x_m=%.6f y_m=%.6f planned_floor_m=%.6f root_floor_m=%.6f residual_m=%.6f density_minus_0p1_m=%.9f density_plane_m=%.9f density_plus_0p1_m=%.9f\n",
|
||||
passage.stableId.c_str(),label,distance,pose.x,pose.y,
|
||||
pose.floorZ,root,
|
||||
root-pose.floorZ,
|
||||
densityMetres(pose.x,pose.y,pose.floorZ-0.1),
|
||||
densityMetres(pose.x,pose.y,pose.floorZ),
|
||||
@@ -3240,6 +3284,13 @@ namespace StandaloneStreamBenchmark
|
||||
{
|
||||
constexpr double CornerBlendMetres=0.50;
|
||||
constexpr double WallReliefMetres=0.21;
|
||||
const auto smoothMax=[](double left,double right,double radius)
|
||||
{
|
||||
if(!(radius>0.0))return std::max(left,right);
|
||||
const double h=std::clamp(radius-std::abs(left-right),0.0,radius)
|
||||
/radius;
|
||||
return std::max(left,right)+h*h*h*(radius/6.0);
|
||||
};
|
||||
for(std::size_t passageIndex=0;
|
||||
passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||
{
|
||||
@@ -3247,9 +3298,12 @@ namespace StandaloneStreamBenchmark
|
||||
if(passage.controlPoints.size()<2)continue;
|
||||
const auto& route=allPassageSmoothRoutes[passageIndex];
|
||||
if(route.size()<2)continue;
|
||||
double bestDistance=std::numeric_limits<double>::infinity();
|
||||
double bestRoundDistance=0.0,bestCenterZ=0.0;
|
||||
std::size_t bestSegment=std::numeric_limits<std::size_t>::max();
|
||||
double bestRoundDistance=std::numeric_limits<double>::infinity();
|
||||
double bestFloorCenterDistanceSquared=
|
||||
std::numeric_limits<double>::infinity();
|
||||
double bestCenterZ=0.0,bestFloorCenterZ=0.0,bestRadius=0.0;
|
||||
std::size_t bestTubeSegment=std::numeric_limits<std::size_t>::max();
|
||||
std::size_t bestFloorSegment=std::numeric_limits<std::size_t>::max();
|
||||
for(std::size_t segment=1;segment<route.size();++segment)
|
||||
{
|
||||
const auto& a=route[segment-1];
|
||||
@@ -3259,57 +3313,67 @@ namespace StandaloneStreamBenchmark
|
||||
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
|
||||
const double lengthSquared=dx*dx+dy*dy+dz*dz;
|
||||
if(lengthSquared<=1.0e-12)continue;
|
||||
const double horizontalLengthSquared=dx*dx+dy*dy;
|
||||
const double horizontalT=horizontalLengthSquared>1.0e-12
|
||||
?((xMetres-a.x)*dx+(yMetres-a.y)*dy)
|
||||
/horizontalLengthSquared
|
||||
:std::clamp((zMetres-a.z)*dz/lengthSquared,0.0,1.0);
|
||||
const double floorAlong=std::lerp(a.distance,b.distance,horizontalT);
|
||||
const double t=std::clamp(((xMetres-a.x)*dx+(yMetres-a.y)*dy
|
||||
+(zMetres-a.z)*dz)/lengthSquared,0.0,1.0);
|
||||
const double centerX=a.x+t*dx,centerY=a.y+t*dy,centerZ=a.z+t*dz;
|
||||
const double radius=std::lerp(a.radius,b.radius,t);
|
||||
const double horizontalLengthSquared=dx*dx+dy*dy;
|
||||
const double floorT=horizontalLengthSquared>1.0e-12
|
||||
?std::clamp(((xMetres-a.x)*dx+(yMetres-a.y)*dy)
|
||||
/horizontalLengthSquared,0.0,1.0):t;
|
||||
const double ex=xMetres-centerX,ey=yMetres-centerY,
|
||||
ez=zMetres-centerZ;
|
||||
const double roundDistance=std::sqrt(ex*ex+ey*ey+ez*ez)-radius;
|
||||
const double floorCenterDistanceSquared=ex*ex+ey*ey+ez*ez;
|
||||
const auto floorPose=interpolatePassageRoute(route,floorAlong);
|
||||
const double floorCenterZ=passageFloorAtXY(passageIndex,
|
||||
xMetres,yMetres,std::lerp(a.floorZ,b.floorZ,floorT));
|
||||
const double roundDistance=std::sqrt((xMetres-centerX)*(xMetres-centerX)
|
||||
+(yMetres-centerY)*(yMetres-centerY)
|
||||
+(zMetres-centerZ)*(zMetres-centerZ))-radius;
|
||||
const double floorDistance=floorCenterZ-zMetres;
|
||||
const double h=std::max(CornerBlendMetres-
|
||||
std::abs(roundDistance-floorDistance),0.0)/CornerBlendMetres;
|
||||
const double flatDistance=std::max(roundDistance,floorDistance)
|
||||
+h*h*h*(CornerBlendMetres/6.0);
|
||||
if(flatDistance<bestDistance)
|
||||
xMetres,yMetres,floorPose.floorZ);
|
||||
// A self-overlap can place another route span's open bore
|
||||
// through this nominal floor probe. Such a point is interior,
|
||||
// not a missing floor shell, even when a different span is
|
||||
// nearest by a few floating-point ulps.
|
||||
if(smoothMax(roundDistance,floorCenterZ-zMetres,
|
||||
CornerBlendMetres)<=0.0)
|
||||
return true;
|
||||
if(floorCenterDistanceSquared<bestFloorCenterDistanceSquared
|
||||
||(floorCenterDistanceSquared==bestFloorCenterDistanceSquared
|
||||
&&segment<bestFloorSegment))
|
||||
{
|
||||
bestFloorCenterDistanceSquared=floorCenterDistanceSquared;
|
||||
bestFloorCenterZ=floorCenterZ;
|
||||
bestFloorSegment=segment;
|
||||
}
|
||||
if(roundDistance<bestRoundDistance
|
||||
||(roundDistance==bestRoundDistance
|
||||
&&segment<bestTubeSegment))
|
||||
{
|
||||
bestDistance=flatDistance;
|
||||
bestRoundDistance=roundDistance;
|
||||
bestCenterZ=centerZ;
|
||||
bestSegment=segment;
|
||||
bestRadius=radius;
|
||||
bestTubeSegment=segment;
|
||||
}
|
||||
}
|
||||
if(bestSegment==std::numeric_limits<std::size_t>::max())continue;
|
||||
const auto& a=route[bestSegment-1];
|
||||
const auto& b=route[bestSegment];
|
||||
if(bestTubeSegment==std::numeric_limits<std::size_t>::max())continue;
|
||||
if(!std::isfinite(bestFloorCenterDistanceSquared))continue;
|
||||
const double floorCenterZ=bestFloorCenterZ;
|
||||
const double floorDistance=bestFloorCenterZ-zMetres;
|
||||
// The composer uses the nearest round tube segment, then the
|
||||
// nearest route station for its continuous floor plane.
|
||||
const auto& a=route[bestTubeSegment-1];
|
||||
const auto& b=route[bestTubeSegment];
|
||||
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
|
||||
const double lengthSquared=dx*dx+dy*dy+dz*dz;
|
||||
if(lengthSquared<=1.0e-12)continue;
|
||||
const double t=std::clamp(((xMetres-a.x)*dx+(yMetres-a.y)*dy
|
||||
+(zMetres-a.z)*dz)/lengthSquared,0.0,1.0);
|
||||
const double radius=std::lerp(a.radius,b.radius,t);
|
||||
const double centerX=a.x+t*dx,centerY=a.y+t*dy;
|
||||
const double centerZ=a.z+t*dz;
|
||||
const double distanceToCenter=std::max(1.0e-9,
|
||||
bestRoundDistance+radius);
|
||||
bestRoundDistance+bestRadius);
|
||||
const double verticalWallAlignment=std::clamp(
|
||||
-(zMetres-centerZ)/distanceToCenter,0.0,1.0);
|
||||
const auto& aFloor=route[bestSegment-1];
|
||||
const auto& bFloor=route[bestSegment];
|
||||
const double floorDx=bFloor.x-aFloor.x;
|
||||
const double floorDy=bFloor.y-aFloor.y;
|
||||
const double horizontalSquared=floorDx*floorDx+floorDy*floorDy;
|
||||
const double floorT=horizontalSquared>1.0e-12
|
||||
?std::clamp(((xMetres-aFloor.x)*floorDx
|
||||
+(yMetres-aFloor.y)*floorDy)/horizontalSquared,0.0,1.0):t;
|
||||
const double floorCenterZ=passageFloorAtXY(passageIndex,
|
||||
xMetres,yMetres,std::lerp(aFloor.floorZ,bFloor.floorZ,floorT));
|
||||
const double wallElevationMetres=zMetres-floorCenterZ;
|
||||
const double crestFade=std::clamp((1.0-wallElevationMetres)/0.25,
|
||||
0.0,1.0);
|
||||
@@ -3325,11 +3389,8 @@ namespace StandaloneStreamBenchmark
|
||||
const double roughRoundDistance=bestRoundDistance
|
||||
+static_cast<double>(std::clamp(relief,-1.0f,1.0f))
|
||||
*WallReliefMetres*wallReliefWeight;
|
||||
const double floorDistance=floorCenterZ-zMetres;
|
||||
const double blend=std::max(CornerBlendMetres-
|
||||
std::abs(roughRoundDistance-floorDistance),0.0)/CornerBlendMetres;
|
||||
const double shapedDistance=std::max(roughRoundDistance,floorDistance)
|
||||
+blend*blend*blend*(CornerBlendMetres/6.0);
|
||||
const double shapedDistance=smoothMax(roughRoundDistance,
|
||||
floorDistance,CornerBlendMetres);
|
||||
if(shapedDistance<=0.0)return true;
|
||||
}
|
||||
return false;
|
||||
@@ -4129,9 +4190,9 @@ namespace StandaloneStreamBenchmark
|
||||
std::uint64_t floorMouthBlendSkips=0,wallMouthBlendSkips=0;
|
||||
std::uint64_t interiorSamples=0,interiorViolations=0;
|
||||
std::vector<double> openAirDistancesMetres;
|
||||
std::array<std::array<double,4>,8> firstViolations{};
|
||||
std::array<std::array<double,10>,8> firstViolations{};
|
||||
std::size_t firstViolationCount=0;
|
||||
std::array<std::array<double,4>,8> firstWallViolations{};
|
||||
std::array<std::array<double,13>,8> firstWallViolations{};
|
||||
std::size_t firstWallViolationCount=0;
|
||||
};
|
||||
constexpr double PassageShellAuditSpacingMetres=0.25;
|
||||
@@ -4142,13 +4203,10 @@ namespace StandaloneStreamBenchmark
|
||||
{
|
||||
constexpr double CornerBlendMetres=0.50;
|
||||
constexpr double ReliefBoundMetres=0.21;
|
||||
const auto smoothstep=[](double value)
|
||||
{
|
||||
value=std::clamp(value,0.0,1.0);
|
||||
return value*value*(3.0-2.0*value);
|
||||
};
|
||||
bool ambiguous=false,expectedSolid=false,mouthBlend=false;
|
||||
if(passageInteriorWinsAt(xMetres,yMetres,zMetres))return 0;
|
||||
double nearestWallShellDistance=std::numeric_limits<double>::infinity();
|
||||
bool mouthBlend=false;
|
||||
if(passageInteriorWinsAt(xMetres,yMetres,zMetres))
|
||||
return std::pair<int,double>{0,nearestWallShellDistance};
|
||||
for(std::size_t passageIndex=0;
|
||||
passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||
{
|
||||
@@ -4165,17 +4223,20 @@ namespace StandaloneStreamBenchmark
|
||||
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
|
||||
const double lengthSquared=dx*dx+dy*dy+dz*dz;
|
||||
if(lengthSquared<=1.0e-12)continue;
|
||||
const double horizontalLengthSquared=dx*dx+dy*dy;
|
||||
const double horizontalT=horizontalLengthSquared>1.0e-12
|
||||
?((xMetres-ax)*dx+(yMetres-ay)*dy)
|
||||
/horizontalLengthSquared
|
||||
:std::clamp((zMetres-az)*dz/lengthSquared,0.0,1.0);
|
||||
const double floorAlong=std::lerp(a.distance,b.distance,horizontalT);
|
||||
const double t=std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy
|
||||
+(zMetres-az)*dz)/lengthSquared,0.0,1.0);
|
||||
const double centerX=ax+t*dx,centerY=ay+t*dy,centerZ=az+t*dz;
|
||||
const double radius=std::lerp(a.radius,b.radius,t);
|
||||
const double horizontalSquared=dx*dx+dy*dy;
|
||||
const double floorT=horizontalSquared>1.0e-12
|
||||
?std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy)
|
||||
/horizontalSquared,0.0,1.0):t;
|
||||
const double floorCenterZ=passageFloorAtXY(passageIndex,
|
||||
xMetres,yMetres,std::lerp(a.floorZ,b.floorZ,floorT));
|
||||
const double along=std::lerp(a.distance,b.distance,t);
|
||||
const auto floorPose=interpolatePassageRoute(path,floorAlong);
|
||||
const double floorCenterZ=passageFloorAtXY(passageIndex,
|
||||
xMetres,yMetres,floorPose.floorZ);
|
||||
const double roundDistance=std::sqrt((xMetres-centerX)*(xMetres-centerX)
|
||||
+(yMetres-centerY)*(yMetres-centerY)
|
||||
+(zMetres-centerZ)*(zMetres-centerZ))-radius;
|
||||
@@ -4189,19 +4250,33 @@ namespace StandaloneStreamBenchmark
|
||||
if(flatDistance<=0.0)continue;
|
||||
const double fade=std::clamp(mouthDistance/5.0,0.0,1.0);
|
||||
const double smoothFade=fade*fade*(3.0-2.0*fade);
|
||||
const double floorBlend=smoothstep(floorDistance/0.25);
|
||||
const double wallElevation=-floorDistance;
|
||||
const double wallThickness=wallElevation>=0.0&&wallElevation<=1.0
|
||||
?smoothFade:0.0;
|
||||
const double thickness=0.75*std::max(wallThickness,
|
||||
smoothFade*floorBlend);
|
||||
if(flatDistance+ReliefBoundMetres<thickness)expectedSolid=true;
|
||||
else if(flatDistance-ReliefBoundMetres<thickness)ambiguous=true;
|
||||
?smoothFade*0.75:0.0;
|
||||
if(wallThickness>1.0e-9)
|
||||
{
|
||||
const double shellRadius=wallThickness*0.5;
|
||||
const double capCenter=1.0-shellRadius;
|
||||
const double x=flatDistance-shellRadius;
|
||||
const double boxHalfHeight=capCenter*0.5;
|
||||
const double boxX=std::abs(x)-shellRadius;
|
||||
const double boxY=std::abs(wallElevation-boxHalfHeight)
|
||||
-boxHalfHeight;
|
||||
const double boxDistance=std::min(std::max(boxX,boxY),0.0)
|
||||
+std::hypot(std::max(boxX,0.0),std::max(boxY,0.0));
|
||||
const double capDistance=std::hypot(x,wallElevation-capCenter)
|
||||
-shellRadius;
|
||||
nearestWallShellDistance=std::min(nearestWallShellDistance,
|
||||
std::min(boxDistance,capDistance));
|
||||
}
|
||||
else if(mouthDistance<5.0)mouthBlend=true;
|
||||
}
|
||||
}
|
||||
if(expectedSolid)return 1;
|
||||
return ambiguous||mouthBlend?-1:0;
|
||||
if(nearestWallShellDistance<-ReliefBoundMetres)
|
||||
return std::pair<int,double>{1,nearestWallShellDistance};
|
||||
if(nearestWallShellDistance<ReliefBoundMetres||mouthBlend)
|
||||
return std::pair<int,double>{-1,nearestWallShellDistance};
|
||||
return std::pair<int,double>{0,nearestWallShellDistance};
|
||||
};
|
||||
const auto auditPassageShell=[&](const VoxelForgeCore::World& Field,
|
||||
const VoxelForgeCore::World& Native,
|
||||
@@ -4216,16 +4291,66 @@ namespace StandaloneStreamBenchmark
|
||||
constexpr std::array<std::array<double,2>,5> floorOffsets{{
|
||||
{{0.0,0.0}},{{0.30,0.0}},{{-0.30,0.0}},
|
||||
{{0.0,0.30}},{{0.0,-0.30}}}};
|
||||
const auto expectedFloorThickness=[&](double along,double floorDistance)
|
||||
const auto expectedFloorShellMarginAt=[&](double xMetres,
|
||||
double yMetres,double zMetres)
|
||||
{
|
||||
const double mouthDistance=std::max(0.0,std::min(along,
|
||||
totalRouteLength-along));
|
||||
const double fade=std::clamp(mouthDistance/5.0,0.0,1.0);
|
||||
const double smoothFade=fade*fade*(3.0-2.0*fade);
|
||||
const double floorBlend=std::clamp(floorDistance/0.25,0.0,1.0);
|
||||
const double smoothFloorBlend=floorBlend*floorBlend
|
||||
*(3.0-2.0*floorBlend);
|
||||
return 0.75*smoothFade*smoothFloorBlend;
|
||||
constexpr double CornerBlendMetres=0.50;
|
||||
double minimumShellDistance=std::numeric_limits<double>::infinity();
|
||||
for(std::size_t candidatePassage=0;
|
||||
candidatePassage<allPassageSmoothRoutes.size();++candidatePassage)
|
||||
{
|
||||
const auto& candidateRoute=
|
||||
allPassageSmoothRoutes[candidatePassage];
|
||||
if(candidateRoute.size()<2)continue;
|
||||
const double candidateLength=candidateRoute.back().distance;
|
||||
for(std::size_t candidateSegment=1;
|
||||
candidateSegment<candidateRoute.size();++candidateSegment)
|
||||
{
|
||||
const auto& a=candidateRoute[candidateSegment-1];
|
||||
const auto& b=candidateRoute[candidateSegment];
|
||||
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
|
||||
const double lengthSquared=dx*dx+dy*dy+dz*dz;
|
||||
if(lengthSquared<=1.0e-12)continue;
|
||||
const double t=std::clamp(((xMetres-a.x)*dx
|
||||
+(yMetres-a.y)*dy+(zMetres-a.z)*dz)/lengthSquared,
|
||||
0.0,1.0);
|
||||
const double horizontalLengthSquared=dx*dx+dy*dy;
|
||||
const double floorT=horizontalLengthSquared>1.0e-12
|
||||
?((xMetres-a.x)*dx+(yMetres-a.y)*dy)
|
||||
/horizontalLengthSquared:t;
|
||||
const double centerX=a.x+t*dx,centerY=a.y+t*dy,
|
||||
centerZ=a.z+t*dz;
|
||||
const double radius=std::lerp(a.radius,b.radius,t);
|
||||
const double along=std::lerp(a.distance,b.distance,t);
|
||||
const double floorAlong=std::lerp(a.distance,b.distance,floorT);
|
||||
const double plannedFloor=passageFloorAtXY(candidatePassage,
|
||||
xMetres,yMetres,interpolatePassageRoute(candidateRoute,
|
||||
floorAlong).floorZ);
|
||||
const double roundDistance=std::sqrt(
|
||||
(xMetres-centerX)*(xMetres-centerX)
|
||||
+(yMetres-centerY)*(yMetres-centerY)
|
||||
+(zMetres-centerZ)*(zMetres-centerZ))-radius;
|
||||
const double floorDistance=plannedFloor-zMetres;
|
||||
const double blend=std::max(CornerBlendMetres
|
||||
-std::abs(roundDistance-floorDistance),0.0)
|
||||
/CornerBlendMetres;
|
||||
const double flatDistance=std::max(roundDistance,floorDistance)
|
||||
+blend*blend*blend*(CornerBlendMetres/6.0);
|
||||
const double mouthDistance=std::max(0.0,std::min(along,
|
||||
candidateLength-along));
|
||||
const double fade=std::clamp(mouthDistance/5.0,0.0,1.0);
|
||||
const double smoothFade=fade*fade*(3.0-2.0*fade);
|
||||
const double floorBlend=std::clamp(floorDistance/0.25,0.0,1.0);
|
||||
const double smoothFloorBlend=floorBlend*floorBlend
|
||||
*(3.0-2.0*floorBlend);
|
||||
const double thickness=0.75*smoothFade*smoothFloorBlend;
|
||||
const double shellDistance=std::max(-flatDistance,
|
||||
flatDistance-thickness);
|
||||
minimumShellDistance=std::min(minimumShellDistance,
|
||||
shellDistance);
|
||||
}
|
||||
}
|
||||
return minimumShellDistance;
|
||||
};
|
||||
for(std::size_t segment=1;segment<route.size();++segment)
|
||||
{
|
||||
@@ -4249,14 +4374,20 @@ namespace StandaloneStreamBenchmark
|
||||
for(const auto& offset:floorOffsets)
|
||||
for(const double depth:PassageShellAuditDepthsMetres)
|
||||
{
|
||||
const double horizontalLengthSquared=dx*dx+dy*dy;
|
||||
const double sampleX=centerX+offset[0];
|
||||
const double sampleY=centerY+offset[1];
|
||||
const double floorT=horizontalLengthSquared>1.0e-12
|
||||
?std::clamp(((sampleX-a.x)*dx+(sampleY-a.y)*dy)
|
||||
/horizontalLengthSquared,0.0,1.0):t;
|
||||
const double floorZ=passageFloorAtXY(passageIndex,
|
||||
sampleX,sampleY,std::lerp(a.floorZ,b.floorZ,floorT));
|
||||
double floorZ=passageFloorAtXY(passageIndex,
|
||||
sampleX,sampleY,interpolatePassageRoute(route,along).floorZ);
|
||||
FPassageRouteProjection floorProjection;
|
||||
for(int projectionStep=0;projectionStep<2;++projectionStep)
|
||||
{
|
||||
floorProjection=projectPassageRoute(route,sampleX,sampleY,
|
||||
floorZ-depth);
|
||||
const auto floorPose=interpolatePassageRoute(route,
|
||||
floorProjection.centerAlong);
|
||||
floorZ=passageFloorAtXY(passageIndex,sampleX,sampleY,
|
||||
floorPose.floorZ);
|
||||
}
|
||||
const VoxelForgeCore::Vec3d position{
|
||||
sampleX/coreMetres,sampleY/coreMetres,
|
||||
(floorZ-depth)/coreMetres};
|
||||
@@ -4271,8 +4402,14 @@ namespace StandaloneStreamBenchmark
|
||||
{
|
||||
++audit.openAirSamples;
|
||||
audit.openAirDistancesMetres.push_back(along);
|
||||
const bool shellExpected=depth+0.011
|
||||
<expectedFloorThickness(along,depth);
|
||||
const double shellMargin=expectedFloorShellMarginAt(
|
||||
sampleX,sampleY,sampleZ);
|
||||
if(std::abs(shellMargin)<=0.011)
|
||||
{
|
||||
++audit.floorMouthBlendSkips;
|
||||
continue;
|
||||
}
|
||||
const bool shellExpected=shellMargin<-0.011;
|
||||
const float density=Field.densityAt(position);
|
||||
if((shellExpected&&density>=PassageShellDensityTolerance)
|
||||
||(!shellExpected&&density<-PassageShellDensityTolerance))
|
||||
@@ -4280,7 +4417,10 @@ namespace StandaloneStreamBenchmark
|
||||
++audit.floorViolations;
|
||||
if(audit.firstViolationCount<audit.firstViolations.size())
|
||||
audit.firstViolations[audit.firstViolationCount++]={
|
||||
sampleX,sampleY,sampleZ,density};
|
||||
sampleX,sampleY,sampleZ,density,along,depth,
|
||||
floorZ,floorProjection.centerAlong,
|
||||
floorProjection.tubeAlong,
|
||||
shellMargin};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4294,8 +4434,8 @@ namespace StandaloneStreamBenchmark
|
||||
}
|
||||
const double nx=-dy/tangentLength,ny=dx/tangentLength;
|
||||
const double floorDepth=passageTubeDepthAt(radius,bodyFloorDepth);
|
||||
const double floorZ=passageFloorAtXY(passageIndex,centerX,centerY,
|
||||
std::lerp(a.floorZ,b.floorZ,t));
|
||||
double floorZ=passageFloorAtXY(passageIndex,centerX,centerY,
|
||||
interpolatePassageRoute(route,along).floorZ);
|
||||
struct FWallProbe {double side=0.0,height=0.0;bool roof=false;};
|
||||
const std::array<FWallProbe,5> wallProbes{{
|
||||
{0.0,0.0,true},{-1.0,0.25,false},{1.0,0.25,false},
|
||||
@@ -4313,12 +4453,21 @@ namespace StandaloneStreamBenchmark
|
||||
const double wallRadius=std::sqrt(wallRadiusSquared);
|
||||
sampleX+=nx*probe.side*(wallRadius+0.375);
|
||||
sampleY+=ny*probe.side*(wallRadius+0.375);
|
||||
for(int projectionStep=0;projectionStep<2;++projectionStep)
|
||||
{
|
||||
sampleZ=floorZ+probe.height;
|
||||
const auto floorProjection=projectPassageRoute(route,
|
||||
sampleX,sampleY,sampleZ);
|
||||
const auto floorPose=interpolatePassageRoute(route,
|
||||
floorProjection.centerAlong);
|
||||
floorZ=passageFloorAtXY(passageIndex,sampleX,sampleY,
|
||||
floorPose.floorZ);
|
||||
}
|
||||
sampleZ=floorZ+probe.height;
|
||||
}
|
||||
const VoxelForgeCore::Vec3d wallPosition{sampleX/coreMetres,
|
||||
sampleY/coreMetres,sampleZ/coreMetres};
|
||||
if(passageInteriorWinsAt(sampleX,sampleY,sampleZ,
|
||||
passageIndex,b.segmentIndex))
|
||||
if(passageInteriorWinsAt(sampleX,sampleY,sampleZ))
|
||||
{
|
||||
++audit.wallInteriorWins;
|
||||
continue;
|
||||
@@ -4328,8 +4477,8 @@ namespace StandaloneStreamBenchmark
|
||||
++audit.wallSamples;
|
||||
audit.openAirDistancesMetres.push_back(along);
|
||||
const float density=Field.densityAt(wallPosition);
|
||||
const int shellExpected=expectedPassageShellAt(
|
||||
sampleX,sampleY,sampleZ);
|
||||
const auto [shellExpected,wallShellMargin]=
|
||||
expectedPassageShellAt(sampleX,sampleY,sampleZ);
|
||||
if(shellExpected>=0
|
||||
&&((shellExpected==1
|
||||
&&density>=PassageShellDensityTolerance)
|
||||
@@ -4340,7 +4489,9 @@ namespace StandaloneStreamBenchmark
|
||||
if(audit.firstWallViolationCount
|
||||
<audit.firstWallViolations.size())
|
||||
audit.firstWallViolations[audit.firstWallViolationCount++]={
|
||||
sampleX,sampleY,sampleZ,density};
|
||||
sampleX,sampleY,sampleZ,density,along,probe.height,
|
||||
probe.side,centerX,centerY,centerZ,floorZ,radius,
|
||||
wallShellMargin};
|
||||
}
|
||||
else if(shellExpected<0)
|
||||
++audit.wallMouthBlendSkips;
|
||||
@@ -4533,23 +4684,41 @@ namespace StandaloneStreamBenchmark
|
||||
&&shellAudit.interiorViolations==0
|
||||
?"PASS":"FAIL");
|
||||
for(std::size_t i=0;i<shellAudit.firstViolationCount;++i)
|
||||
std::printf("[PassageShellAuditSample] id=%s kind=floor_missing xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
||||
passage.stableId.c_str(),shellAudit.firstViolations[i][0],
|
||||
shellAudit.firstViolations[i][1],shellAudit.firstViolations[i][2],
|
||||
shellAudit.firstViolations[i][3]);
|
||||
{
|
||||
const auto& sample=shellAudit.firstViolations[i];
|
||||
const VoxelForgeCore::Vec3d position{
|
||||
sample[0]/coreMetres,sample[1]/coreMetres,
|
||||
sample[2]/coreMetres};
|
||||
std::printf("[PassageShellAuditSample] id=%s kind=floor_missing xyz_m=(%.3f,%.3f,%.3f) density=%.6f native_density=%.6f native_path=%s probe_s_m=%.3f projected_center_s_m=%.3f projected_tube_s_m=%.3f depth_m=%.3f floor_z_m=%.3f expected_shell_margin_m=%.6f\n",
|
||||
passage.stableId.c_str(),sample[0],sample[1],sample[2],sample[3],
|
||||
blockedWorld.densityAt(position),
|
||||
blockedWorld.densityDispatchPathAt(position).c_str(),
|
||||
sample[4],sample[7],sample[8],sample[5],sample[6],sample[9]);
|
||||
}
|
||||
for(std::size_t i=0;i<shellAudit.firstWallViolationCount;++i)
|
||||
{
|
||||
const auto& sample=shellAudit.firstWallViolations[i];
|
||||
const int expected=expectedPassageShellAt(sample[0],sample[1],sample[2]);
|
||||
const auto [expected,wallShellMargin]=
|
||||
expectedPassageShellAt(sample[0],sample[1],sample[2]);
|
||||
const bool passageInterior=passageInteriorWinsAt(
|
||||
sample[0],sample[1],sample[2]);
|
||||
std::printf("[PassageShellAuditSample] id=%s kind=wall_missing xyz_m=(%.3f,%.3f,%.3f) density=%.6f expected=%d passage_interior=%s native_density=%.6f\n",
|
||||
std::printf("[PassageShellAuditSample] id=%s kind=wall_missing xyz_m=(%.3f,%.3f,%.3f) density=%.6f expected=%d wall_shell_margin_m=%.6f passage_interior=%s native_density=%.6f probe_s_m=%.3f probe_height_m=%.3f probe_side=%.0f route_center_m=(%.3f,%.3f,%.3f) floor_z_m=%.3f radius_m=%.3f dispatch=%s passage_source=%.6f unclamped=%.6f\n",
|
||||
passage.stableId.c_str(),shellAudit.firstWallViolations[i][0],
|
||||
shellAudit.firstWallViolations[i][1],shellAudit.firstWallViolations[i][2],
|
||||
shellAudit.firstWallViolations[i][3],expected,
|
||||
shellAudit.firstWallViolations[i][3],expected,wallShellMargin,
|
||||
passageInterior?"true":"false",
|
||||
blockedWorld.densityAt({sample[0]/coreMetres,
|
||||
sample[1]/coreMetres,sample[2]/coreMetres}));
|
||||
sample[1]/coreMetres,sample[2]/coreMetres}),
|
||||
sample[4],sample[5],sample[6],sample[7],sample[8],sample[9],
|
||||
sample[10],sample[11],
|
||||
Shape.densityDispatchPathAt({sample[0]/coreMetres,
|
||||
sample[1]/coreMetres,sample[2]/coreMetres}).c_str(),
|
||||
static_cast<double>(Shape.passageSourceDensityAt({
|
||||
sample[0]/coreMetres,sample[1]/coreMetres,
|
||||
sample[2]/coreMetres})),
|
||||
static_cast<double>(Shape.unclampedDensityAt({
|
||||
sample[0]/coreMetres,sample[1]/coreMetres,
|
||||
sample[2]/coreMetres})));
|
||||
}
|
||||
for(std::size_t i=0;i<passageAudit.firstBlockedCount;++i)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user