wip(assembler): round L33 -- SurfaceWorld can be an upper strate; FloorProfileBlend measures seams
Route planning for an upper SurfaceWorld failed on long candidate chords over steep relief; chords are now capped to the arrival/departure radii on an 8 m centre grid with an origin-proximity cost (21.5 m walk, about 4 deg). The departure passage starts at the SW ground and tunnels down through rock. FI -> SW -> TN fixture: all 4 styles pass every passage audit on both compilers. FloorProfileBlend now checks endpoint height/tangent seams (step and tangent-step negative controls detected). Owner world hashes unchanged; 30/30 determinism; cost within noise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
1 parent
d0158c6019
commit
b7a47bef95
17 files changed
+1116
-32
No files matched your search
@@ -788,10 +788,13 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
cfg->distanceMaximumSubvoxels):8*qPerMetre;
|
||||
const std::int64_t placementRadius=std::max<std::int64_t>(1,outer);
|
||||
// Leave room for the capsule and bounded wall relief at the floor/wall seam.
|
||||
const std::int64_t mouthRadius=std::max<std::int64_t>(static_cast<std::int64_t>(std::llround(1.5*qPerMetre)),
|
||||
const bool upperIsSurfaceWorld=upper.generatorKind=="SurfaceWorld";
|
||||
const std::int64_t minimumMouthRadius=static_cast<std::int64_t>(std::llround(
|
||||
(upperIsSurfaceWorld?2.0:1.5)*qPerMetre));
|
||||
const std::int64_t mouthRadius=std::max<std::int64_t>(minimumMouthRadius,
|
||||
cfg?cfg->mouthRadiusSubvoxels:0);
|
||||
const std::int64_t midRadius=std::max<std::int64_t>(
|
||||
static_cast<std::int64_t>(std::llround(1.5*qPerMetre)),
|
||||
static_cast<std::int64_t>(std::llround((upperIsSurfaceWorld?3.5:1.5)*qPerMetre)),
|
||||
cfg?cfg->midRadiusSubvoxels:mouthRadius);
|
||||
const std::int64_t reachMinimum=cfg?std::max<std::int64_t>(0,cfg->reachMinimumSubvoxels):8*qPerMetre;
|
||||
const std::int64_t reachMaximum=cfg?std::max<std::int64_t>(reachMinimum,cfg->reachMaximumSubvoxels):24*qPerMetre;
|
||||
@@ -813,6 +816,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
std::array<std::int64_t,3> upperMouth{},lowerMouth{};
|
||||
const bool hasUpperRoute=connection==0&&routeAnchor(upper,true,upperMouth);
|
||||
const bool hasLowerRoute=connection==0&&routeAnchor(lower,false,lowerMouth);
|
||||
const bool surfaceGroundDeparture=hasUpperRoute&&upperIsSurfaceWorld;
|
||||
const ResolvedLayoutAnchorV2* passageAnchor=nullptr;
|
||||
if(connection==0)
|
||||
for(const auto& anchor:upper.anchors)
|
||||
@@ -870,6 +874,17 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
std::max<std::int64_t>(0,cfg?cfg->cascadeLedgeSubvoxels:0)
|
||||
*static_cast<std::int64_t>(cfg?cfg->cascadeSteps:4)),
|
||||
mouthRadius+2*qPerMetre,outerRingRadius);
|
||||
if(surfaceGroundDeparture)
|
||||
{
|
||||
const double departureRadius=std::hypot(static_cast<double>(upperMouth[0]),
|
||||
static_cast<double>(upperMouth[1]));
|
||||
const std::int64_t clearRingRadius=static_cast<std::int64_t>(std::ceil(
|
||||
departureRadius+mouthRadius+0.5*qPerMetre));
|
||||
const std::int64_t maximumRingRadius=std::max<std::int64_t>(1,
|
||||
placementRadius-qPerMetre);
|
||||
ringRadius=std::min(maximumRingRadius,
|
||||
std::max(ringRadius,clearRingRadius));
|
||||
}
|
||||
if(anchoredIslandPassage)
|
||||
ringRadius=std::min(ringRadius,mouthRadius+qPerMetre/2);
|
||||
std::uint32_t phase=static_cast<std::uint32_t>(PassageSeed(seed,
|
||||
@@ -890,6 +905,23 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
{bestAlignment=alignment;phase=candidate;}
|
||||
}
|
||||
}
|
||||
if(hasUpperRoute&&upperIsSurfaceWorld)
|
||||
{
|
||||
const auto& directionAtPhase=directions[phase];
|
||||
const std::int64_t radialAlignment=upperMouth[0]*directionAtPhase[0]
|
||||
+upperMouth[1]*directionAtPhase[1];
|
||||
if(radialAlignment<0)
|
||||
{
|
||||
std::int64_t bestOutwardAlignment=std::numeric_limits<std::int64_t>::min();
|
||||
for(std::uint32_t candidate=0;candidate<directions.size();++candidate)
|
||||
{
|
||||
const std::int64_t alignment=upperMouth[0]*directions[candidate][0]
|
||||
+upperMouth[1]*directions[candidate][1];
|
||||
if(alignment>bestOutwardAlignment)
|
||||
{bestOutwardAlignment=alignment;phase=candidate;}
|
||||
}
|
||||
}
|
||||
}
|
||||
bool clockwise=(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
|
||||
connection,0x44495245u)&1u)!=0;
|
||||
if(anchoredSpiral&&passageAnchor&&hasUpperTangent)
|
||||
@@ -1159,10 +1191,23 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
if(hasAnchorHelix&&helixHorizontal==0)
|
||||
{error=passage.stableId+" anchor spiral has no horizontal reach";return false;}
|
||||
passage.controlPoints.reserve(xy.size());
|
||||
const std::int64_t surfacePortalRadius=surfaceGroundDeparture
|
||||
?std::min(mouthRadius,static_cast<std::int64_t>(std::llround(1.5*qPerMetre)))
|
||||
:mouthRadius;
|
||||
const std::uint64_t surfaceMouthBlendLength=static_cast<std::uint64_t>(
|
||||
std::max<std::int64_t>(8*qPerMetre,2*mouthRadius));
|
||||
for(std::size_t i=0;i<xy.size();++i)
|
||||
{
|
||||
const std::int64_t radius=(i<=1||i==lowerMouthIndex
|
||||
std::int64_t radius=(i<=1||i==lowerMouthIndex
|
||||
||i+1>=xy.size()-1)?mouthRadius:midRadius;
|
||||
if(surfaceGroundDeparture&&i<lowerMouthIndex)
|
||||
{
|
||||
const double t=std::clamp(static_cast<double>(cumulative[i])
|
||||
/surfaceMouthBlendLength,0.0,1.0);
|
||||
const double smooth=t*t*(3.0-2.0*t);
|
||||
radius=surfacePortalRadius+static_cast<std::int64_t>(std::llround(
|
||||
(midRadius-surfacePortalRadius)*smooth));
|
||||
}
|
||||
std::int64_t playerZ=0;
|
||||
if(i>lowerMouthIndex)
|
||||
playerZ=lowerLandingPoints[i-lowerMouthIndex-1][2];
|
||||
@@ -1183,9 +1228,30 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
point.positionSubvoxels={xy[i][0],xy[i][1],playerZ+radius-playerCenterAboveFloorQ};
|
||||
point.radiusSubvoxels=radius;passage.controlPoints.push_back(point);
|
||||
}
|
||||
passage.controlPoints.front().positionSubvoxels[2]=upperMouth[2]+mouthRadius-playerCenterAboveFloorQ;
|
||||
passage.controlPoints[lowerMouthIndex].positionSubvoxels[2]=
|
||||
lowerMouth[2]+mouthRadius-playerCenterAboveFloorQ;
|
||||
if(surfaceGroundDeparture)
|
||||
std::printf("[ResolvedSurfaceWorldDepartureMouth] passage=%s ground_floor_z_m=%.3f portal_radius_m=%.3f tunnel_radius_m=%.3f blend_length_m=%.3f departure_xy_m=(%.3f,%.3f) first_tunnel_xy_m=(%.3f,%.3f) first_tunnel_floor_z_m=%.3f first_tunnel_radius_m=%.3f\n",
|
||||
passage.stableId.c_str(),(upperMouth[2]-static_cast<std::int64_t>(std::llround(0.88*qPerMetre)))/static_cast<double>(qPerMetre),
|
||||
surfacePortalRadius/static_cast<double>(qPerMetre),
|
||||
midRadius/static_cast<double>(qPerMetre),
|
||||
surfaceMouthBlendLength/static_cast<double>(qPerMetre),
|
||||
upperMouth[0]/static_cast<double>(qPerMetre),
|
||||
upperMouth[1]/static_cast<double>(qPerMetre),
|
||||
passage.controlPoints.size()>1?passage.controlPoints[1].positionSubvoxels[0]
|
||||
/static_cast<double>(qPerMetre):0.0,
|
||||
passage.controlPoints.size()>1?passage.controlPoints[1].positionSubvoxels[1]
|
||||
/static_cast<double>(qPerMetre):0.0,
|
||||
passage.controlPoints.size()>1?
|
||||
(passage.controlPoints[1].positionSubvoxels[2]
|
||||
-passage.controlPoints[1].radiusSubvoxels+playerCenterAboveFloorQ
|
||||
-static_cast<std::int64_t>(std::llround(0.88*qPerMetre)))
|
||||
/static_cast<double>(qPerMetre):0.0,
|
||||
passage.controlPoints.size()>1?passage.controlPoints[1].radiusSubvoxels
|
||||
/static_cast<double>(qPerMetre):0.0);
|
||||
passage.controlPoints.front().positionSubvoxels[2]=upperMouth[2]
|
||||
+passage.controlPoints.front().radiusSubvoxels-playerCenterAboveFloorQ;
|
||||
passage.controlPoints[lowerMouthIndex].positionSubvoxels[2]=lowerMouth[2]
|
||||
+passage.controlPoints[lowerMouthIndex].radiusSubvoxels
|
||||
-playerCenterAboveFloorQ;
|
||||
layout.passages.push_back(std::move(passage));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1309,6 +1309,10 @@ public:
|
||||
double MaxUnmarkedFloorDirectionSlopeDelta = 0.0;
|
||||
double MaxMarkedFloorSlopeDelta = 0.0;
|
||||
double MaxFloorCurveDeviation = 0.0;
|
||||
double MaxFloorProfileEndpointValueError = 0.0;
|
||||
double MaxFloorProfileEndpointSlopeError = 0.0;
|
||||
bool FloorProfileEndpointStepControlDetected = false;
|
||||
bool FloorProfileEndpointSlopeControlDetected = false;
|
||||
double LocalPrimitiveSeconds = 0.0;
|
||||
double LocalLineBinSeconds = 0.0;
|
||||
double CandidateMergeSeconds = 0.0;
|
||||
@@ -1380,6 +1384,8 @@ public:
|
||||
double FloorCurveStartBlend = 0.0;
|
||||
double FloorCurveEndBlend = 0.0;
|
||||
double FloorCurveMaxDeviation = 0.0;
|
||||
double FloorCurveEndpointValueError = 0.0;
|
||||
double FloorCurveEndpointSlopeError = 0.0;
|
||||
};
|
||||
|
||||
struct FInterval { int32 Lo = 0, Hi = -1; };
|
||||
@@ -1517,6 +1523,7 @@ public:
|
||||
if(NodeCount>160000000ull)
|
||||
{Error="shape interval region exceeds the 160-million-node safety bound";return false;}
|
||||
Stats={};Stats.SignNodes=NodeCount;
|
||||
AuditFloorProfileBlendNegativeControls();
|
||||
|
||||
auto Stage=Clock::now();
|
||||
BuildLayerBounds();
|
||||
@@ -1622,13 +1629,19 @@ public:
|
||||
static_cast<unsigned long long>(Stats.RoomFloorJoinCount));
|
||||
const double SmoothUnionGrowthBound=JoinBlendRadius/6.0;
|
||||
const bool bFloorSlopePass=Stats.MaxUnmarkedFloorSlopeDelta<=0.75;
|
||||
const bool bFloorBlendPass=Stats.MaxFloorCurveDeviation<=2.0;
|
||||
const bool bFloorBlendPass=Stats.MaxFloorProfileEndpointValueError<=0.01
|
||||
&&Stats.MaxFloorProfileEndpointSlopeError<=0.001
|
||||
&&Stats.FloorProfileEndpointStepControlDetected
|
||||
&&Stats.FloorProfileEndpointSlopeControlDetected;
|
||||
std::printf("[FloorSlopeJoinAudit] raw_unmarked_max_delta=%.5f vox generated_tangent_delta=%.5f vox bend_direction_slope_change=%.5f vox marked_max_delta=%.5f vox marked_joins=%llu threshold=0.750 status=%s\n",
|
||||
Stats.MaxRawUnmarkedFloorSlopeDelta,Stats.MaxUnmarkedFloorSlopeDelta,
|
||||
Stats.MaxUnmarkedFloorDirectionSlopeDelta,Stats.MaxMarkedFloorSlopeDelta,
|
||||
static_cast<unsigned long long>(Stats.MarkedFloorSlopeJoins),bFloorSlopePass?"PASS":"FAIL");
|
||||
std::printf("[FloorProfileBlend] c1_segments=%llu max_curve_deviation=%.3f vox threshold=2.000 status=%s\n",
|
||||
std::printf("[FloorProfileBlend] c1_segments=%llu max_chord_deviation=%.3f vox informational endpoint_value_residual=%.8f vox endpoint_slope_residual=%.8f vox_per_m thresholds=(0.010,0.001) negative_controls=%s/%s status=%s\n",
|
||||
static_cast<unsigned long long>(Stats.CurvedFloorSegments),Stats.MaxFloorCurveDeviation,
|
||||
Stats.MaxFloorProfileEndpointValueError,Stats.MaxFloorProfileEndpointSlopeError,
|
||||
Stats.FloorProfileEndpointStepControlDetected?"step_detected":"step_missed",
|
||||
Stats.FloorProfileEndpointSlopeControlDetected?"slope_detected":"slope_missed",
|
||||
bFloorBlendPass?"PASS":"FAIL");
|
||||
std::printf("[TunnelRadiusAudit] shared_joins=%llu legacy_fixed_segment_max_step=%.4f vox smoothed_profile_max_step=%.4f vox tapered_endpoint_max_delta=%.6f vox radius_slope_join_delta=%.8f floor_endpoint_max_delta=%.6f vox floor_slope_join_delta=%.5f unmarked_floor_slope_delta=%.5f marked_floor_slope_delta=%.5f marked_slope_joins=%llu smooth_union_growth_bound=%.4f vox thresholds=(0.005,0.000001,0.005,0.25) status=%s\n",
|
||||
static_cast<unsigned long long>(Stats.CapsuleJoinCount),Stats.LegacySegmentRadiusStep,
|
||||
@@ -2751,8 +2764,23 @@ private:
|
||||
auto& flags= crossing.needsColumn
|
||||
?PassageColumnSegmentFlags[crossing.passageIndex]
|
||||
:PassageShortSpanSegmentFlags[crossing.passageIndex];
|
||||
const std::size_t first=std::min(crossing.firstSegment,flags.size());
|
||||
const std::size_t last=std::min(crossing.lastSegment,flags.empty()?0:flags.size()-1);
|
||||
bool surfaceWorldUpper=false;
|
||||
if(crossing.passageIndex<ResolvedLayout->passages.size())
|
||||
{
|
||||
const std::int32_t strate=ResolvedLayout->passages[
|
||||
crossing.passageIndex].upperStrateIndex;
|
||||
const auto upper=std::find_if(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[strate](const auto& value)
|
||||
{return value.strateIndex==strate;});
|
||||
surfaceWorldUpper=upper!=ResolvedLayout->strates.end()
|
||||
&&upper->generatorKind=="SurfaceWorld";
|
||||
}
|
||||
const std::size_t shortSpanPadding=!crossing.needsColumn
|
||||
&&surfaceWorldUpper&&!flags.empty()?2:0;
|
||||
const std::size_t first=std::min(crossing.firstSegment>shortSpanPadding
|
||||
?crossing.firstSegment-shortSpanPadding:0,flags.size());
|
||||
const std::size_t last=std::min(crossing.lastSegment+shortSpanPadding,
|
||||
flags.empty()?0:flags.size()-1);
|
||||
if(!flags.empty()&&first<=last)
|
||||
std::fill(flags.begin()+first,flags.begin()+last+1,1);
|
||||
}
|
||||
@@ -3011,6 +3039,11 @@ private:
|
||||
ResolvedLayout->coreVoxelSizeMicrometres);
|
||||
const auto& a=passage.controlPoints[SegmentIndex];
|
||||
const auto& b=passage.controlPoints[SegmentIndex+1];
|
||||
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[&passage](const auto& value)
|
||||
{return value.strateIndex==passage.upperStrateIndex;});
|
||||
const bool surfaceWorldUpper=upperStratum!=ResolvedLayout->strates.end()
|
||||
&&upperStratum->generatorKind=="SurfaceWorld";
|
||||
const double ax=static_cast<double>(a.positionSubvoxels[0]);
|
||||
const double ay=static_cast<double>(a.positionSubvoxels[1]);
|
||||
const double dx=static_cast<double>(b.positionSubvoxels[0])-ax;
|
||||
@@ -3031,7 +3064,8 @@ private:
|
||||
+PassageBridgeDeckOffsetMetres(PassageIndex,SegmentIndex)*qPerMetre;
|
||||
const double relief=std::clamp(static_cast<double>(VF_FieldSurfaceRelief(X,Y,Z,
|
||||
static_cast<std::uint32_t>(Owner?Owner->Seed:0))),-1.0,1.0);
|
||||
const double halfWidth=(3.30+0.20*relief)*qPerMetre;
|
||||
const double halfWidth=(surfaceWorldUpper?3.65+0.10*relief:3.30+0.20*relief)
|
||||
*qPerMetre;
|
||||
const double thickness=(4.0+1.0*relief)*qPerMetre;
|
||||
const double horizontalDistance=DeterministicSqrt((qx-centerX)*(qx-centerX)
|
||||
+(qy-centerY)*(qy-centerY));
|
||||
@@ -3214,6 +3248,11 @@ private:
|
||||
++passageIndex)
|
||||
{
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[&passage](const auto& value)
|
||||
{return value.strateIndex==passage.upperStrateIndex;});
|
||||
const bool surfaceWorldUpper=upperStratum!=ResolvedLayout->strates.end()
|
||||
&&upperStratum->generatorKind=="SurfaceWorld";
|
||||
if(passage.controlPoints.size()<2)continue;
|
||||
std::vector<FPathSample> samples;
|
||||
samples.reserve(passage.controlPoints.size()*2);
|
||||
@@ -3303,14 +3342,32 @@ private:
|
||||
}
|
||||
double maximumOffsetSubvoxels=0.0;
|
||||
double maximumBoreSubvoxels=0.0;
|
||||
for(std::size_t index=startSample;index<=endSample;++index)
|
||||
double minimumCrossingZ=std::min(crossing.startPoint[2],crossing.endPoint[2]);
|
||||
double maximumCrossingZ=std::max(crossing.startPoint[2],crossing.endPoint[2]);
|
||||
for(std::size_t sampleIndex=0;sampleIndex<samples.size();++sampleIndex)
|
||||
{
|
||||
const auto& point=samples[index].point;
|
||||
const auto& sample=samples[sampleIndex];
|
||||
// The audit and density field use whole control segments, including
|
||||
// the neighboring segments that extend the column through its caps.
|
||||
// Size the column from that same segment window instead of only the
|
||||
// unsupported half-metre samples inside the original gap.
|
||||
if(surfaceWorldUpper)
|
||||
{
|
||||
if(sample.segment<crossing.firstSegment
|
||||
||sample.segment>crossing.lastSegment)continue;
|
||||
}
|
||||
else if(sampleIndex<startSample||sampleIndex>endSample)continue;
|
||||
const auto& point=sample.point;
|
||||
const double dx=point[0]-crossing.centerXSubvoxels;
|
||||
const double dy=point[1]-crossing.centerYSubvoxels;
|
||||
maximumOffsetSubvoxels=std::max(maximumOffsetSubvoxels,
|
||||
DeterministicSqrt(dx*dx+dy*dy));
|
||||
maximumBoreSubvoxels=std::max(maximumBoreSubvoxels,point[3]);
|
||||
if(surfaceWorldUpper)
|
||||
{
|
||||
minimumCrossingZ=std::min(minimumCrossingZ,point[2]);
|
||||
maximumCrossingZ=std::max(maximumCrossingZ,point[2]);
|
||||
}
|
||||
}
|
||||
const double maximumOffsetMetres=maximumOffsetSubvoxels/qPerMetre;
|
||||
const double maximumBoreMetres=maximumBoreSubvoxels/qPerMetre;
|
||||
@@ -3324,12 +3381,10 @@ private:
|
||||
nominalRadiusMetres*qPerMetre));
|
||||
crossing.maximumRadiusSubvoxels=crossing.minimumRadiusSubvoxels
|
||||
+static_cast<std::int64_t>(std::ceil(11.25*qPerMetre));
|
||||
const double startZ=crossing.startPoint[2];
|
||||
const double endZ=crossing.endPoint[2];
|
||||
crossing.topZSubvoxels=static_cast<std::int64_t>(std::llround(
|
||||
std::max(startZ,endZ)+5.0*qPerMetre));
|
||||
maximumCrossingZ+5.0*qPerMetre));
|
||||
crossing.bottomZSubvoxels=static_cast<std::int64_t>(std::llround(
|
||||
std::min(startZ,endZ)-6.0*qPerMetre));
|
||||
minimumCrossingZ-6.0*qPerMetre));
|
||||
}
|
||||
PassageVoidCrossings.push_back(crossing);
|
||||
if(crossing.needsColumn)
|
||||
@@ -3350,7 +3405,7 @@ private:
|
||||
crossing.connectedAtStart&&crossing.connectedAtEnd?"PLANNED":"UNATTACHED");
|
||||
}
|
||||
else
|
||||
std::printf("[PassageShortGroundedSpanPlan] id=%s segments=%zu..%zu span_m=%.3f threshold_m=%.3f connected=%d/%d width_m=6.000 thickness_m=3.000..5.000 status=%s\n",
|
||||
std::printf("[PassageShortGroundedSpanPlan] id=%s segments=%zu..%zu span_m=%.3f threshold_m=%.3f connected=%d/%d width_m=7.300 thickness_m=3.000..5.000 status=%s\n",
|
||||
passage.stableId.c_str(),crossing.firstSegment,crossing.lastSegment,
|
||||
gapLength,shortSpanMaximumMetres,static_cast<int>(crossing.connectedAtStart),
|
||||
static_cast<int>(crossing.connectedAtEnd),
|
||||
@@ -4193,7 +4248,10 @@ public:
|
||||
static_cast<unsigned long long>(S.NearZero),static_cast<unsigned long long>(S.AxisLocked));
|
||||
}
|
||||
const bool bFloorProfilePass=Stats.MaxUnmarkedFloorSlopeDelta<=0.75
|
||||
&&Stats.MaxFloorCurveDeviation<=2.0;
|
||||
&&Stats.MaxFloorProfileEndpointValueError<=0.01
|
||||
&&Stats.MaxFloorProfileEndpointSlopeError<=0.001
|
||||
&&Stats.FloorProfileEndpointStepControlDetected
|
||||
&&Stats.FloorProfileEndpointSlopeControlDetected;
|
||||
return bDetailPass&&bGridPass&&bThinSheetPass&&bFloorProfilePass;
|
||||
}
|
||||
|
||||
@@ -4949,6 +5007,44 @@ private:
|
||||
return Result;
|
||||
}
|
||||
|
||||
static double EvaluateFloorProfileSlope(double A,double B,double TangentA,double TangentB,
|
||||
double Length,double StartBlend,double EndBlend,double S)
|
||||
{
|
||||
if(S<0.0)return TangentA;
|
||||
if(S>Length)return TangentB;
|
||||
const double Secant=(B-A)/Length;
|
||||
double Result=Secant;
|
||||
const auto CorrectionSlope=[](double DeltaSlope,double T)
|
||||
{return DeltaSlope*(1.0-4.0*T+3.0*T*T);};
|
||||
if(S<StartBlend)
|
||||
Result+=CorrectionSlope(TangentA-Secant,S/StartBlend);
|
||||
if(Length-S<EndBlend)
|
||||
Result+=CorrectionSlope(TangentB-Secant,(Length-S)/EndBlend);
|
||||
return Result;
|
||||
}
|
||||
|
||||
void AuditFloorProfileBlendNegativeControls()
|
||||
{
|
||||
constexpr double A=2.0,B=11.0,TangentA=0.35,TangentB=-0.20;
|
||||
constexpr double Length=20.0,StartBlend=7.0,EndBlend=7.0;
|
||||
constexpr double Step=3.0,SlopeStep=0.25;
|
||||
constexpr double ValueThreshold=0.01,SlopeThreshold=0.001;
|
||||
const double endValue=EvaluateFloorProfile(A,B,TangentA,TangentB,
|
||||
Length,StartBlend,EndBlend,Length);
|
||||
const double startSlope=EvaluateFloorProfileSlope(A,B,TangentA,TangentB,
|
||||
Length,StartBlend,EndBlend,0.0);
|
||||
Stats.FloorProfileEndpointStepControlDetected=
|
||||
std::abs((endValue+Step)-B)>ValueThreshold;
|
||||
Stats.FloorProfileEndpointSlopeControlDetected=
|
||||
std::abs((startSlope+SlopeStep)-TangentA)>SlopeThreshold;
|
||||
std::printf("[FloorProfileBlendNegativeControls] endpoint_step=%.3f_vox tangent_step=%.3f_vox_per_m detected=%s/%s status=%s\n",
|
||||
Step,SlopeStep,
|
||||
Stats.FloorProfileEndpointStepControlDetected?"true":"false",
|
||||
Stats.FloorProfileEndpointSlopeControlDetected?"true":"false",
|
||||
Stats.FloorProfileEndpointStepControlDetected
|
||||
&&Stats.FloorProfileEndpointSlopeControlDetected?"PASS":"FAIL");
|
||||
}
|
||||
|
||||
static double FloorSurfaceRelief(double X,double Y,uint32 Seed)
|
||||
{
|
||||
// A shared XY field keeps independently authored floor clips from ending on
|
||||
@@ -5014,6 +5110,16 @@ private:
|
||||
const double Linear=std::lerp(FloorA,FloorB,T);
|
||||
P.FloorCurveMaxDeviation=std::max(P.FloorCurveMaxDeviation,std::abs(Curve-Linear));
|
||||
}
|
||||
P.FloorCurveEndpointValueError=std::max(
|
||||
std::abs(EvaluateFloorProfile(FloorA,FloorB,TangentA,TangentB,Length,
|
||||
P.FloorCurveStartBlend,P.FloorCurveEndBlend,0.0)-FloorA),
|
||||
std::abs(EvaluateFloorProfile(FloorA,FloorB,TangentA,TangentB,Length,
|
||||
P.FloorCurveStartBlend,P.FloorCurveEndBlend,Length)-FloorB));
|
||||
P.FloorCurveEndpointSlopeError=std::max(
|
||||
std::abs(EvaluateFloorProfileSlope(FloorA,FloorB,TangentA,TangentB,Length,
|
||||
P.FloorCurveStartBlend,P.FloorCurveEndBlend,0.0)-TangentA),
|
||||
std::abs(EvaluateFloorProfileSlope(FloorA,FloorB,TangentA,TangentB,Length,
|
||||
P.FloorCurveStartBlend,P.FloorCurveEndBlend,Length)-TangentB));
|
||||
}
|
||||
|
||||
double FloorAt(const FPrimitive& P,double X,double Y) const
|
||||
@@ -5224,6 +5330,12 @@ private:
|
||||
++Stats.CurvedFloorSegments;
|
||||
Stats.MaxFloorCurveDeviation=std::max(Stats.MaxFloorCurveDeviation,
|
||||
Primitives.back().FloorCurveMaxDeviation);
|
||||
Stats.MaxFloorProfileEndpointValueError=std::max(
|
||||
Stats.MaxFloorProfileEndpointValueError,
|
||||
Primitives.back().FloorCurveEndpointValueError);
|
||||
Stats.MaxFloorProfileEndpointSlopeError=std::max(
|
||||
Stats.MaxFloorProfileEndpointSlopeError,
|
||||
Primitives.back().FloorCurveEndpointSlopeError);
|
||||
}
|
||||
FitCapsuleRadiiAtEndpoints(Primitives.back(),Points[I],R0,Points[I+1],R1);
|
||||
SetCapsuleRadiusProfile(Primitives.back(),Points[I],R0,RadiusSlope0,
|
||||
@@ -5348,6 +5460,12 @@ private:
|
||||
++Stats.CurvedFloorSegments;
|
||||
Stats.MaxFloorCurveDeviation=std::max(Stats.MaxFloorCurveDeviation,
|
||||
Primitives.back().FloorCurveMaxDeviation);
|
||||
Stats.MaxFloorProfileEndpointValueError=std::max(
|
||||
Stats.MaxFloorProfileEndpointValueError,
|
||||
Primitives.back().FloorCurveEndpointValueError);
|
||||
Stats.MaxFloorProfileEndpointSlopeError=std::max(
|
||||
Stats.MaxFloorProfileEndpointSlopeError,
|
||||
Primitives.back().FloorCurveEndpointSlopeError);
|
||||
FitCapsuleRadiiAtEndpoints(Primitives.back(),Points[I],RadiusA,Points[I+1],RadiusB);
|
||||
SetCapsuleRadiusProfile(Primitives.back(),Points[I],RadiusA,RadiusSlopeA,
|
||||
Points[I+1],RadiusB,RadiusSlopeB);
|
||||
|
||||
Reference in new issue
Block a user