core: cave planner holds for 16 seeds; mouth joints planned from the resolved mouth (L135, v236)
Mouth floor/profile carried into the first/last edges (fixes seeds 1 and 2); fixed a Q16/Q32 scale bug in the mouth clearance scorer; shortest grade-safe handles; bounded cave AABBs and hoisted edge-BVH normalisation. Seeds 0-15 pass all composed audits and negative controls; fast tier, Clang/GCC W1/W8/W12 green. Near-ready still +3.9..+13.7% vs assembler/world (Surface World +3.9..+8.2% with no caves). Not for merge. 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
a3c8e3687d
commit
2d89023933
25 files changed
+2516
-235
No files matched your search
@@ -138,6 +138,9 @@ namespace VoxelForgeCore
|
||||
bool passageComposerProfiled = false;
|
||||
std::uint64_t passageTileFeatureCount = 0;
|
||||
std::uint64_t passageTileRouteSegmentCandidates = 0;
|
||||
std::uint64_t nativeCaveFieldSamples = 0;
|
||||
std::uint64_t cavernRoomCandidates = 0;
|
||||
std::uint64_t cavernEdgeCandidates = 0;
|
||||
std::uint64_t resolvedBoundaryBandQueries = 0;
|
||||
std::uint64_t passageComposerQueries = 0;
|
||||
std::uint64_t passageIterations = 0;
|
||||
@@ -498,8 +501,16 @@ namespace VoxelForgeCore
|
||||
// voxels for every broadphase and proof bound. Version 227 uses the exact smooth-max
|
||||
// support width/6 when deriving that reach. Version 228 composes deterministic cave
|
||||
// graphs from fixed passage mouths and the owner Tunnel Network settings. Version 229
|
||||
// budgets cave floor support; 230 records a shared backbone floor profile and joint cap.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 230;
|
||||
// budgets cave floor support; 230 records a shared backbone floor profile and joint cap;
|
||||
// 231 carries passage tangents and floor slopes through cave portal lead-ins and keeps
|
||||
// floor backing local to the walk footprint, with depth measured along the floor normal;
|
||||
// 233 bounds passage ring jitter to the authored placement disk; 234 aligns route mouth
|
||||
// anchors with capsule foot height and carries support through the SurfaceWorld mouth;
|
||||
// 235 plans the cave backbone as a sampled curve with both passage mouth tangents and
|
||||
// preserves open cave lanes through passage shell overlaps; 236 binds passage mouths to
|
||||
// the planned graph route, preserves its walk corridor through shell overlaps, and keeps
|
||||
// boundary-blended host rock from widening the authored passage wall.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 236;
|
||||
|
||||
enum class FieldChannel : std::uint32_t
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -255,6 +255,7 @@ public:
|
||||
{
|
||||
double signedDistance=std::numeric_limits<double>::infinity();
|
||||
double shellSignedDistance=std::numeric_limits<double>::infinity();
|
||||
double floorShellSignedDistance=std::numeric_limits<double>::infinity();
|
||||
double roundSignedDistance=std::numeric_limits<double>::infinity();
|
||||
double centerZSubvoxels=0.0,floorCenterZSubvoxels=0.0;
|
||||
double radiusSubvoxels=0.0,floorDepthSubvoxels=0.0;
|
||||
@@ -308,6 +309,14 @@ public:
|
||||
stampIndices_=shape.BuildStampTileList(minimumSubvoxels_,maximumSubvoxels_);
|
||||
cavernFeatureIndices_=shape.BuildCavernFeatureTileList(
|
||||
minimumSubvoxels_,maximumSubvoxels_);
|
||||
if(profileStats_)
|
||||
for(const std::uint32_t index:cavernFeatureIndices_)
|
||||
{
|
||||
if(index>=shape.CavernRuntime.size())continue;
|
||||
const auto& feature=shape.CavernRuntime[index];
|
||||
if(feature.room)++profileStats_->cavernRoomCandidates;
|
||||
else if(feature.edge)++profileStats_->cavernEdgeCandidates;
|
||||
}
|
||||
passageRoutes_.assign(shape.ResolvedLayout
|
||||
?shape.ResolvedLayout->passages.size():0,0);
|
||||
passageRouteSegments_.resize(passageRoutes_.size());
|
||||
@@ -1206,11 +1215,15 @@ public:
|
||||
auto* passageProfile=passageTileScopeMatches
|
||||
?passageTileQueries->profileStats():nullptr;
|
||||
if(passageProfile)++passageProfile->passageComposerQueries;
|
||||
if(passageProfile&&nativeTunnelFieldDensity
|
||||
!=std::numeric_limits<float>::lowest())
|
||||
++passageProfile->nativeCaveFieldSamples;
|
||||
|
||||
struct FNearestTubeSample
|
||||
{
|
||||
double signedDistance=std::numeric_limits<double>::infinity();
|
||||
double shellSignedDistance=std::numeric_limits<double>::infinity();
|
||||
double floorShellSignedDistance=std::numeric_limits<double>::infinity();
|
||||
double roundSignedDistance=std::numeric_limits<double>::infinity();
|
||||
double centerZ=0.0,floorCenterZ=0.0,radius=0.0,floorDepth=0.0;
|
||||
double t=0.0,floorT=0.0;
|
||||
@@ -1220,13 +1233,32 @@ public:
|
||||
};
|
||||
double unionDistance=std::numeric_limits<double>::infinity();
|
||||
double unionShellDistance=std::numeric_limits<double>::infinity();
|
||||
double unionFloorShellDistance=std::numeric_limits<double>::infinity();
|
||||
double plannedTunnelPassageShellDistance=std::numeric_limits<double>::infinity();
|
||||
const std::uint32_t reliefSeed=static_cast<std::uint32_t>(Owner?Owner->Seed:0);
|
||||
const auto* queryStratum=FindBoundStratumForManagerZ(Z);
|
||||
const bool plannedTunnelStratum=queryStratum&&queryStratum->Resolved
|
||||
&&queryStratum->Resolved->generatorKind=="TunnelNetwork"
|
||||
&&queryStratum->Resolved->recipeId=="Core.Caves.PrimitiveField"
|
||||
&&queryStratum->Resolved->recipeVersion==1;
|
||||
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
|
||||
++passageIndex)
|
||||
{
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
if(passage.controlPoints.size()<2)continue;
|
||||
const bool surfaceDepartureLanding=queryStratum&&queryStratum->Resolved
|
||||
&&queryStratum->Resolved->lowerInterfaceCapabilities.surfaceToTunnelLandingSupport
|
||||
&&passage.upperStrateIndex==queryStratum->StrateIndex;
|
||||
const auto floorThicknessWithLanding=[&](double floorThickness,
|
||||
double smoothFloorBlend,double alongMetres)
|
||||
{
|
||||
if(!surfaceDepartureLanding)return floorThickness;
|
||||
const double fade=std::clamp(1.0-alongMetres/8.0,0.0,1.0);
|
||||
const double smoothFade=fade*fade*(3.0-2.0*fade);
|
||||
const double landingThickness=0.50*qPerMetre
|
||||
*smoothFloorBlend*smoothFade;
|
||||
return std::max(floorThickness,landingThickness);
|
||||
};
|
||||
const bool passageTargetsNativeCaveField=queryStratum&&queryStratum->Resolved
|
||||
&&queryStratum->Resolved->passageCapabilities.hasNativeCaveField
|
||||
&&passage.lowerStrateIndex==queryStratum->StrateIndex;
|
||||
@@ -1235,6 +1267,8 @@ public:
|
||||
&&queryStratum->Resolved->generatorKind=="TunnelNetwork"
|
||||
&&queryStratum->Resolved->recipeId=="Core.Caves.PrimitiveField"
|
||||
&&queryStratum->Resolved->recipeVersion==1;
|
||||
const double mouthShellBlendMetres=passageTargetsPlannedTunnelNetwork
|
||||
?2.0:MouthShellBlendMetres;
|
||||
// Legacy native cave fields own the floor and mouth carve on their
|
||||
// lower-strate boundary. The planner's TunnelNetwork field instead
|
||||
// needs the shared passage composer to carry its incoming mouth open.
|
||||
@@ -1390,7 +1424,7 @@ public:
|
||||
const double mouthDistanceMetres=std::max(0.0,std::min(alongMetres,
|
||||
constants.totalPathLengthMetres-alongMetres));
|
||||
const double shellFade=std::clamp(mouthDistanceMetres/
|
||||
MouthShellBlendMetres,0.0,1.0);
|
||||
mouthShellBlendMetres,0.0,1.0);
|
||||
const double smoothShellFade=shellFade*shellFade
|
||||
*(3.0-2.0*shellFade);
|
||||
const double floorDistanceMetres=(floorCenterZ-floorDepth-qz)
|
||||
@@ -1408,12 +1442,16 @@ public:
|
||||
const double pastMouthMetres=std::max(0.0,alongMetres
|
||||
-constants.totalPathLengthMetres);
|
||||
const double extensionFade=std::clamp(1.0
|
||||
-pastMouthMetres/MouthShellBlendMetres,0.0,1.0);
|
||||
-pastMouthMetres/mouthShellBlendMetres,0.0,1.0);
|
||||
floorExtensionFade=extensionFade*extensionFade
|
||||
*(3.0-2.0*extensionFade);
|
||||
}
|
||||
const double floorShellThickness=smoothFloorBlend*shellThickness
|
||||
*floorExtensionFade*smoothShellFade;
|
||||
const double floorShellThickness=floorThicknessWithLanding(
|
||||
smoothFloorBlend*(shellThickness
|
||||
+(passageTargetsPlannedTunnelNetwork?0.25*qPerMetre:0.0))
|
||||
*floorExtensionFade
|
||||
*smoothShellFade,
|
||||
smoothFloorBlend,alongMetres);
|
||||
const double wallShellDistance=PassageRoundedWallShellDistance(
|
||||
flatDistance,wallElevationMetres*qPerMetre,
|
||||
OpenAirSideWallHeightMetres*qPerMetre,mouthWallThickness);
|
||||
@@ -1421,8 +1459,14 @@ public:
|
||||
static_cast<double>(flatDistance)-floorShellThickness);
|
||||
const double localShellDistance=std::min(wallShellDistance,
|
||||
floorShellDistance);
|
||||
if(passageTargetsPlannedTunnelNetwork
|
||||
&&mouthDistanceMetres>=mouthShellBlendMetres)
|
||||
plannedTunnelPassageShellDistance=std::min(
|
||||
plannedTunnelPassageShellDistance,localShellDistance);
|
||||
nearest.shellSignedDistance=std::min(nearest.shellSignedDistance,
|
||||
localShellDistance);
|
||||
nearest.floorShellSignedDistance=std::min(
|
||||
nearest.floorShellSignedDistance,floorShellDistance);
|
||||
};
|
||||
double shellFloorSearchBoundSq=-1.0;
|
||||
VisitNearestPassageSegmentRange(passageIndex,0,lastIndex-1,
|
||||
@@ -1439,6 +1483,14 @@ public:
|
||||
const double crestFade=std::clamp((OpenAirSideWallHeightMetres
|
||||
-wallElevationMetres)/0.25,0.0,1.0);
|
||||
const double smoothCrestFade=crestFade*crestFade*(3.0-2.0*crestFade);
|
||||
const auto& segment=PassageSegmentData[passageIndex][nearest.segmentIndex];
|
||||
const double alongMetres=segment.authoredAlongMetres
|
||||
+nearest.t*segment.authoredLengthMetres;
|
||||
const double mouthDistanceMetres=std::max(0.0,std::min(alongMetres,
|
||||
constants.totalPathLengthMetres-alongMetres));
|
||||
const double shellFade=std::clamp(mouthDistanceMetres/
|
||||
mouthShellBlendMetres,0.0,1.0);
|
||||
const double smoothShellFade=shellFade*shellFade*(3.0-2.0*shellFade);
|
||||
const double wallReliefWeight=(0.05+0.95*(1.0-verticalWallAlignment))
|
||||
*smoothCrestFade;
|
||||
float relief=0.0f;
|
||||
@@ -1457,15 +1509,7 @@ public:
|
||||
const double shapedDistance=PassageFlatBottomDistance(
|
||||
static_cast<float>(roughRoundDistance),nearest.floorCenterZ,qz,
|
||||
nearest.floorDepth,qPerMetre);
|
||||
const auto& segment=PassageSegmentData[passageIndex][nearest.segmentIndex];
|
||||
const auto& floorSegment=PassageSegmentData[passageIndex][nearest.floorSegmentIndex];
|
||||
const double alongMetres=segment.authoredAlongMetres
|
||||
+nearest.t*segment.authoredLengthMetres;
|
||||
const double mouthDistanceMetres=std::max(0.0,std::min(alongMetres,
|
||||
constants.totalPathLengthMetres-alongMetres));
|
||||
const double shellFade=std::clamp(mouthDistanceMetres/
|
||||
MouthShellBlendMetres,0.0,1.0);
|
||||
const double smoothShellFade=shellFade*shellFade*(3.0-2.0*shellFade);
|
||||
// Ease both wall and floor shell into the host ground over five metres.
|
||||
// The floor shell remains below the floor plane throughout that blend.
|
||||
const double floorDistanceMetres=(nearest.floorCenterZ
|
||||
@@ -1483,18 +1527,26 @@ public:
|
||||
const double pastMouthMetres=std::max(0.0,floorAlongMetres
|
||||
-constants.totalPathLengthMetres);
|
||||
const double extensionFade=std::clamp(1.0
|
||||
-pastMouthMetres/MouthShellBlendMetres,0.0,1.0);
|
||||
-pastMouthMetres/mouthShellBlendMetres,0.0,1.0);
|
||||
floorExtensionFade=extensionFade*extensionFade
|
||||
*(3.0-2.0*extensionFade);
|
||||
}
|
||||
const double floorShellThickness=smoothFloorBlend*shellThickness
|
||||
*floorExtensionFade*smoothShellFade;
|
||||
const double floorShellThickness=floorThicknessWithLanding(
|
||||
smoothFloorBlend*(shellThickness
|
||||
+(passageTargetsPlannedTunnelNetwork?0.25*qPerMetre:0.0))
|
||||
*floorExtensionFade
|
||||
*smoothShellFade,
|
||||
smoothFloorBlend,alongMetres);
|
||||
const double wallShellDistance=PassageRoundedWallShellDistance(
|
||||
shapedDistance,wallElevationMetres*qPerMetre,
|
||||
OpenAirSideWallHeightMetres*qPerMetre,mouthWallThickness);
|
||||
const double floorShellDistance=std::max(-static_cast<double>(shapedDistance),
|
||||
static_cast<double>(shapedDistance)-floorShellThickness);
|
||||
nearest.floorShellSignedDistance=std::min(
|
||||
nearest.floorShellSignedDistance,floorShellDistance);
|
||||
unionDistance=std::min(unionDistance,static_cast<double>(shapedDistance));
|
||||
unionFloorShellDistance=std::min(unionFloorShellDistance,
|
||||
nearest.floorShellSignedDistance);
|
||||
const double localShellDistance=std::min(wallShellDistance,
|
||||
floorShellDistance);
|
||||
unionShellDistance=std::min(unionShellDistance,
|
||||
@@ -1503,12 +1555,131 @@ public:
|
||||
|
||||
if(!std::isfinite(unionDistance))return density;
|
||||
const float passageAirDistance=static_cast<float>(-unionDistance/Q);
|
||||
const auto plannedPortalWalkCorridorContains=[&]()
|
||||
{
|
||||
if(!queryStratum||!queryStratum->Resolved
|
||||
||queryStratum->Resolved->generatorKind!="TunnelNetwork"
|
||||
||queryStratum->Resolved->recipeId!="Core.Caves.PrimitiveField"
|
||||
||queryStratum->Resolved->recipeVersion!=1)return false;
|
||||
const VoxelForgeCore::ResolvedLayoutAnchorV2* first=nullptr;
|
||||
const VoxelForgeCore::ResolvedLayoutAnchorV2* second=nullptr;
|
||||
const VoxelForgeCore::ResolvedLayoutAnchorV2* third=nullptr;
|
||||
const VoxelForgeCore::ResolvedLayoutAnchorV2* lastThree=nullptr;
|
||||
const VoxelForgeCore::ResolvedLayoutAnchorV2* lastTwo=nullptr;
|
||||
const VoxelForgeCore::ResolvedLayoutAnchorV2* last=nullptr;
|
||||
std::size_t routeCount=0;
|
||||
for(const auto& anchor:queryStratum->Resolved->anchors)
|
||||
{
|
||||
if(anchor.kind!="route_point"||anchor.values.size()<3)continue;
|
||||
if(routeCount==0)first=&anchor;
|
||||
else if(routeCount==1)second=&anchor;
|
||||
else if(routeCount==2)third=&anchor;
|
||||
lastThree=lastTwo;lastTwo=last;last=&anchor;
|
||||
++routeCount;
|
||||
}
|
||||
if(routeCount<6||!first||!second||!third||!lastThree||!lastTwo||!last
|
||||
||!first->stableId.ends_with("000-arrival")
|
||||
||!second->stableId.ends_with("000-edge-start")
|
||||
||!lastTwo->stableId.ends_with("998-departure-inner")
|
||||
||!last->stableId.ends_with("999-departure"))return false;
|
||||
const double metresPerSubvoxel=1.0/qPerMetre;
|
||||
const auto position=[&](const VoxelForgeCore::ResolvedLayoutAnchorV2& anchor)
|
||||
{
|
||||
return std::array<double,3>{anchor.values[0]*metresPerSubvoxel,
|
||||
anchor.values[1]*metresPerSubvoxel,
|
||||
anchor.values[2]*metresPerSubvoxel};
|
||||
};
|
||||
const double queryX=X*Q/qPerMetre;
|
||||
const double queryY=Y*Q/qPerMetre;
|
||||
const double queryZ=Z*Q/qPerMetre;
|
||||
const auto nearRouteSegment=[&](const VoxelForgeCore::ResolvedLayoutAnchorV2& start,
|
||||
const VoxelForgeCore::ResolvedLayoutAnchorV2& end,double limit,bool fromDeparture,
|
||||
double distanceToDepartureAfterEnd)
|
||||
{
|
||||
const auto a=position(start),b=position(end);
|
||||
const double dx=b[0]-a[0],dy=b[1]-a[1];
|
||||
const double horizontalLength=std::hypot(dx,dy);
|
||||
if(horizontalLength<=1.0e-9)return false;
|
||||
const double t=std::clamp(((queryX-a[0])*dx
|
||||
+(queryY-a[1])*dy)/(horizontalLength*horizontalLength),0.0,1.0);
|
||||
const double along=fromDeparture
|
||||
?distanceToDepartureAfterEnd+(1.0-t)*horizontalLength
|
||||
:distanceToDepartureAfterEnd+t*horizontalLength;
|
||||
if(along>limit)return false;
|
||||
const double routeX=a[0]+t*dx,routeY=a[1]+t*dy;
|
||||
if(std::hypot(queryX-routeX,queryY-routeY)>0.80)return false;
|
||||
const double routeFloor=std::lerp(a[2],b[2],t)-1.13;
|
||||
return queryZ>=routeFloor-0.30&&queryZ<=routeFloor+2.25;
|
||||
};
|
||||
constexpr double PortalWalkLeadMetres=8.0;
|
||||
const auto arrival=position(*first),arrivalInner=position(*second);
|
||||
const double arrivalInnerDistance=std::hypot(
|
||||
arrivalInner[0]-arrival[0],arrivalInner[1]-arrival[1]);
|
||||
if(nearRouteSegment(*first,*second,PortalWalkLeadMetres,false,0.0)
|
||||
||nearRouteSegment(*second,*third,PortalWalkLeadMetres,false,
|
||||
arrivalInnerDistance))return true;
|
||||
const auto departureInner=position(*lastTwo),departure=position(*last);
|
||||
const double departureTailDistance=std::hypot(
|
||||
departure[0]-departureInner[0],departure[1]-departureInner[1]);
|
||||
return nearRouteSegment(*lastThree,*lastTwo,PortalWalkLeadMetres,true,
|
||||
departureTailDistance)
|
||||
||nearRouteSegment(*lastTwo,*last,PortalWalkLeadMetres,true,0.0);
|
||||
};
|
||||
if(plannedTunnelStratum
|
||||
&&nativeTunnelFieldDensity!=std::numeric_limits<float>::lowest()
|
||||
&&nativeTunnelFieldDensity<0.0f)
|
||||
{
|
||||
const double metresPerVoxel=Q/qPerMetre;
|
||||
const double queryZMetres=Z*metresPerVoxel;
|
||||
const double contentBottomMetres=static_cast<double>(
|
||||
queryStratum->Resolved->content.bottomMicrometres)/1000000.0;
|
||||
const double contentTopMetres=static_cast<double>(
|
||||
queryStratum->Resolved->content.topMicrometres)/1000000.0;
|
||||
const double boundaryDistance=std::min(
|
||||
std::abs(queryZMetres-contentBottomMetres),
|
||||
std::abs(queryZMetres-contentTopMetres));
|
||||
const double boundaryJoinBand=std::max(2.0,
|
||||
static_cast<double>(queryStratum->Resolved->transitionWidthMicrometres)
|
||||
/2000000.0);
|
||||
if(boundaryDistance<=boundaryJoinBand
|
||||
&&std::abs(static_cast<double>(passageAirDistance)*metresPerVoxel)
|
||||
<=FloorShellThicknessMetres)
|
||||
// A neighboring stratum can be open at a boundary sample even
|
||||
// though the native graph owns solid host rock there. Keep the
|
||||
// authored passage signed distance through that transition band;
|
||||
// otherwise the neighboring field can widen its wall by a whole
|
||||
// shell thickness. Native graph air still wins above via the
|
||||
// positive nativeTunnelFieldDensity path.
|
||||
return passageAirDistance;
|
||||
}
|
||||
// Air is the union of the native graph and passage voids. Keeping the
|
||||
// signed passage distance outside the void as well gives marching cubes
|
||||
// a real negative distance through the floor instead of the terrain's
|
||||
// saturated deep-rock value.
|
||||
if(nativeTunnelFieldDensity!=std::numeric_limits<float>::lowest()
|
||||
&&plannedTunnelPassageShellDistance<0.0&&unionDistance>0.0)
|
||||
{
|
||||
if(nativeTunnelFieldDensity>=0.0f&&plannedPortalWalkCorridorContains())
|
||||
return std::max(nativeTunnelFieldDensity,passageAirDistance);
|
||||
return std::min(density,
|
||||
static_cast<float>(plannedTunnelPassageShellDistance/Q));
|
||||
}
|
||||
if(nativeTunnelFieldDensity>=0.0f)
|
||||
{
|
||||
// The planner's passage shell remains authoritative through native
|
||||
// cave voids after the explicit mouth fade has ended. Keep this scoped
|
||||
// to the planner-crossing passage shell so nearby portal joints stay open.
|
||||
if(plannedTunnelPassageShellDistance<0.0&&unionDistance>0.0)
|
||||
return std::min(nativeTunnelFieldDensity,
|
||||
static_cast<float>(plannedTunnelPassageShellDistance/Q));
|
||||
constexpr float NativeCaveAirMarginVoxels=0.75f;
|
||||
if(unionDistance>0.0
|
||||
&&nativeTunnelFieldDensity<NativeCaveAirMarginVoxels
|
||||
&&unionFloorShellDistance<0.0)
|
||||
return std::min(nativeTunnelFieldDensity,
|
||||
static_cast<float>(unionFloorShellDistance/Q));
|
||||
return std::max(nativeTunnelFieldDensity,passageAirDistance);
|
||||
}
|
||||
if(nativeFloorSupport!=std::numeric_limits<float>::lowest()
|
||||
&&nativeFloorSupport<0.0f)
|
||||
return std::min(nativeFloorSupport,unionDistance<=0.0
|
||||
@@ -2274,6 +2445,10 @@ public:
|
||||
SurfaceDistanceQ=std::make_unique_for_overwrite<int16[]>(static_cast<size_t>(NodeCount));
|
||||
Substage=Clock::now();
|
||||
BuildPackedRockIntervals(Stats.RockIntervalLines);
|
||||
const auto criticalAirInitialStart=Clock::now();
|
||||
const uint64 criticalAirVoxelsBeforeDetail=ApplyCriticalAirPackedSigns();
|
||||
const double criticalAirInitialSeconds=
|
||||
std::chrono::duration<double>(Clock::now()-criticalAirInitialStart).count();
|
||||
Stats.PackedSignBuildSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
|
||||
// Native density queries for non-heightfield strates use the exact interval
|
||||
// evaluator. Build those native intervals before preparing the tunnel sweeps.
|
||||
@@ -2296,8 +2471,16 @@ public:
|
||||
ApplyDetailBand(Stats.DetailBandLines);
|
||||
Stats.DetailApplySeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
|
||||
Substage=Clock::now();
|
||||
const uint64 criticalAirVoxelsAfterDetail=ApplyCriticalAirPackedSigns();
|
||||
const double criticalAirRepairSeconds=
|
||||
std::chrono::duration<double>(Clock::now()-Substage).count();
|
||||
Substage=Clock::now();
|
||||
Stats.SurfaceProxyNodes+=CompleteSignEdgeDistances();
|
||||
Stats.SignEdgeDistanceSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
|
||||
Substage=Clock::now();
|
||||
const uint64 criticalAirVoxelsAfterSignRepair=ApplyCriticalAirPackedSigns();
|
||||
const double criticalAirSignEdgeRepairSeconds=
|
||||
std::chrono::duration<double>(Clock::now()-Substage).count();
|
||||
Stats.SurfaceSeconds=std::chrono::duration<double>(Clock::now()-Stage).count();
|
||||
// Route validation is world-wide. Detail regions reuse the same authored route data and
|
||||
// do not need to repeat the 5,642-point world audit for every entering tile group.
|
||||
@@ -2329,8 +2512,13 @@ public:
|
||||
Stats.CandidateMergeLines.WallSeconds*1000.0,Stats.CandidateMergeLines.WorkerSeconds*1000.0,
|
||||
Stats.CandidateMergeSeconds*1000.0,Stats.DetailBandLines.WallSeconds*1000.0,
|
||||
Stats.DetailBandLines.WorkerSeconds*1000.0);
|
||||
std::printf("[ShapeSubstage] detail_apply=%.3fms sign_edge_distances=%.3fms passage_validation=%.3fms shared_field_primitives=%.3fms far_parents=%.3fms\n",
|
||||
Stats.DetailApplySeconds*1000.0,Stats.SignEdgeDistanceSeconds*1000.0,
|
||||
std::printf("[ShapeSubstage] detail_apply=%.3fms critical_air_sign_repair=%.3fms critical_air_voxels=%llu/%llu/%llu sign_edge_distances=%.3fms passage_validation=%.3fms shared_field_primitives=%.3fms far_parents=%.3fms\n",
|
||||
Stats.DetailApplySeconds*1000.0,criticalAirInitialSeconds*1000.0
|
||||
+criticalAirRepairSeconds*1000.0+criticalAirSignEdgeRepairSeconds*1000.0,
|
||||
static_cast<unsigned long long>(criticalAirVoxelsBeforeDetail),
|
||||
static_cast<unsigned long long>(criticalAirVoxelsAfterDetail),
|
||||
static_cast<unsigned long long>(criticalAirVoxelsAfterSignRepair),
|
||||
Stats.SignEdgeDistanceSeconds*1000.0,
|
||||
Stats.PassageValidationSeconds*1000.0,Stats.SharedFieldSeconds*1000.0,
|
||||
Stats.FarShapeSeconds*1000.0);
|
||||
std::printf("[ShapeBreakdown] owner_shapes=%.3fms owner_shape_worker=%.3fms owner_bins=%.3fms packed_signs=%.3fms passage_prep=%.3fms candidate_sweeps=%.3fms visual_detail_prep=%.3fms detail_band=%.3fms sign_edge_repair=%.3fms route_validation=%.3fms far_parent_shapes=%.3fms far_shared_primitives=%.3fms far_bins=%.3fms\n",
|
||||
@@ -3402,6 +3590,7 @@ private:
|
||||
{
|
||||
double floorDepthMetres=0.0,totalPathLengthMetres=0.0;
|
||||
double authoredTotalPathLengthMetres=0.0;
|
||||
double upperMouthPlayerCenterHeightMetres=1.13;
|
||||
std::size_t routeProfileSegmentCount=0;
|
||||
double upperMouthX=0.0,upperMouthY=0.0;
|
||||
double upperMouthPlannedFloor=0.0;
|
||||
@@ -3475,7 +3664,8 @@ private:
|
||||
plannedFloor,constants.upperMouthX,constants.upperMouthY,
|
||||
constants.upperMouthPlannedFloor,
|
||||
constants.upperMouthTangentX,constants.upperMouthTangentY,
|
||||
VoxelForgeCore::PassageSpline::UpperMouthFloorBlendRadiusMetres,1.13,
|
||||
VoxelForgeCore::PassageSpline::UpperMouthFloorBlendRadiusMetres,
|
||||
constants.upperMouthPlayerCenterHeightMetres,
|
||||
qPerMetre);
|
||||
const double lowerBlendFloor=VoxelForgeCore::PassageSpline::BlendUpperMouthFloorAtXY(
|
||||
constants.lowerMouthReceivingRoute,queryXSubvoxels,queryYSubvoxels,
|
||||
@@ -3594,7 +3784,7 @@ private:
|
||||
};
|
||||
struct FSegmentNode
|
||||
{
|
||||
std::array<double,3> minimum{},maximum{};
|
||||
std::array<double,3> minimum{},maximum{},minimumNormalized{},maximumNormalized{};
|
||||
std::size_t first=0,end=0;
|
||||
int left=-1,right=-1;
|
||||
};
|
||||
@@ -4174,6 +4364,16 @@ private:
|
||||
return nodeIndex;
|
||||
};
|
||||
buildSegmentNode(buildSegmentNode,0,runtime.segmentOrder.size());
|
||||
const double radius=edge.boreRadiusMicrometres/1000000.0;
|
||||
const double height=edge.boreHeightMicrometres/1000000.0;
|
||||
if(radius>0.0&&height>0.0)
|
||||
for(auto& node:runtime.segmentNodes)
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double scale=axis==2?height:radius;
|
||||
node.minimumNormalized[axis]=node.minimum[axis]/scale;
|
||||
node.maximumNormalized[axis]=node.maximum[axis]/scale;
|
||||
}
|
||||
}
|
||||
CavernRuntime.push_back(runtime);
|
||||
}
|
||||
@@ -4623,6 +4823,12 @@ private:
|
||||
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1);
|
||||
auto& constants=PassageComposerConstants[passageIndex];
|
||||
constants.routeEndControlIndex=routeEnd;
|
||||
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
||||
{return stratum.strateIndex==passage.upperStrateIndex;});
|
||||
if(upperStratum!=ResolvedLayout->strates.end()
|
||||
&&upperStratum->generatorKind=="SurfaceWorld")
|
||||
constants.upperMouthPlayerCenterHeightMetres=0.98;
|
||||
|
||||
using FRawPassageSegment=VoxelForgeCore::PassageSpline::FSegment;
|
||||
std::vector<VoxelForgeCore::PassageSpline::FPoint> controls;
|
||||
@@ -4640,11 +4846,9 @@ private:
|
||||
}
|
||||
const double authoredUpperMouthFloorDepth=std::max(0.0,
|
||||
static_cast<double>(passage.controlPoints.front().positionSubvoxels[2]
|
||||
-passage.upperMouthSubvoxels[2])/qPerMetre+1.13);
|
||||
-passage.upperMouthSubvoxels[2])/qPerMetre
|
||||
+constants.upperMouthPlayerCenterHeightMetres);
|
||||
double upperMouthFloorDepth=authoredUpperMouthFloorDepth;
|
||||
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
||||
{return stratum.strateIndex==passage.upperStrateIndex;});
|
||||
if(upperStratum!=ResolvedLayout->strates.end()
|
||||
&&(upperStratum->generatorKind=="FloatingIslands"
|
||||
||upperStratum->generatorKind=="TunnelNetwork"))
|
||||
@@ -4664,7 +4868,8 @@ private:
|
||||
&&distanceSquared<=std::pow(controls.front()[3]+2.0*qPerMetre,2.0))
|
||||
{
|
||||
upperMouthFloorDepth=std::max(upperMouthFloorDepth,
|
||||
std::max(0.0,(controls.front()[2]-routeHeight)/qPerMetre+1.13));
|
||||
std::max(0.0,(controls.front()[2]-routeHeight)/qPerMetre
|
||||
+constants.upperMouthPlayerCenterHeightMetres));
|
||||
if(upperStratum->generatorKind=="TunnelNetwork")
|
||||
VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute(
|
||||
controls,plannedFloorProfile,0,route,qPerMetre);
|
||||
@@ -4761,6 +4966,26 @@ private:
|
||||
constants.upperMouthTangentY=sampledSegments.front().b[1]
|
||||
-sampledSegments.front().a[1];
|
||||
}
|
||||
const std::string lowerMouthAnchor=passage.stableId+"/lower-mouth";
|
||||
const std::string upperMouthAnchor=passage.stableId+"/upper-mouth";
|
||||
for(const auto& edge:ResolvedLayout->cavernEdges)
|
||||
{
|
||||
if(edge.controlPointsMicrometres.size()<2)continue;
|
||||
if(edge.startAnchorStableId==lowerMouthAnchor)
|
||||
{
|
||||
constants.lowerMouthTangentX=edge.controlPointsMicrometres[1][0]
|
||||
-edge.controlPointsMicrometres[0][0];
|
||||
constants.lowerMouthTangentY=edge.controlPointsMicrometres[1][1]
|
||||
-edge.controlPointsMicrometres[0][1];
|
||||
}
|
||||
if(edge.startAnchorStableId==upperMouthAnchor)
|
||||
{
|
||||
constants.upperMouthTangentX=edge.controlPointsMicrometres[0][0]
|
||||
-edge.controlPointsMicrometres[1][0];
|
||||
constants.upperMouthTangentY=edge.controlPointsMicrometres[0][1]
|
||||
-edge.controlPointsMicrometres[1][1];
|
||||
}
|
||||
}
|
||||
std::vector<FRawPassageSegment> rawSegments=sampledSegments;
|
||||
std::vector<std::array<double,2>> routeRadiusSamples;
|
||||
routeRadiusSamples.push_back({0.0,controls.front()[3]/qPerMetre});
|
||||
@@ -7563,6 +7788,8 @@ private:
|
||||
double floorSupportDepth=0.0,floorSupportFloor=0.0,floorSupportGrade=0.0;
|
||||
const std::string* floorSupportEdgeId=nullptr;
|
||||
const std::string* floorSupportProfileId=nullptr;
|
||||
double lowestOpenEdgeFloor=std::numeric_limits<double>::infinity();
|
||||
const std::string* lowestOpenEdgeProfileId=nullptr;
|
||||
for(const std::uint32_t index:*candidates)
|
||||
{
|
||||
if(index>=CavernRuntime.size())continue;
|
||||
@@ -7570,7 +7797,26 @@ private:
|
||||
const std::int32_t strateIndex=feature.room?feature.room->strateIndex
|
||||
:feature.edge?feature.edge->strateIndex:-1;
|
||||
if(strateIndex!=bound.StrateIndex)continue;
|
||||
// Tile culling keeps the candidate list small, but a tile can still
|
||||
// contain millions of sample points outside a feature's authored
|
||||
// bounds. Reject those points before evaluating room SDFs or walking
|
||||
// an edge's segment BVH. Keep a 1 mm guard for float voxel-coordinate
|
||||
// rounding; the serialized bounds already include all SDF, union,
|
||||
// irregularity, bore, and floor-support reach.
|
||||
constexpr double FeatureBoundsGuardMetres=0.001;
|
||||
bool insideFeatureBounds=true;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double minimum=feature.boundsMinimumMicrometres[axis]/1000000.0
|
||||
-FeatureBoundsGuardMetres;
|
||||
const double maximum=feature.boundsMaximumMicrometres[axis]/1000000.0
|
||||
+FeatureBoundsGuardMetres;
|
||||
if(point[axis]<minimum||point[axis]>maximum)
|
||||
{insideFeatureBounds=false;break;}
|
||||
}
|
||||
if(!insideFeatureBounds)continue;
|
||||
double featureVoid=std::numeric_limits<double>::infinity();
|
||||
double edgeFloor=0.0;
|
||||
if(feature.room)
|
||||
{
|
||||
double normalizedSquared=0.0,meanRadius=0.0;
|
||||
@@ -7655,15 +7901,16 @@ private:
|
||||
?(segment.end[2]-segment.start[2])/horizontal:0.0;
|
||||
}
|
||||
};
|
||||
const std::array<double,3> normalizedPoint={
|
||||
point[0]/radius,point[1]/radius,point[2]/height};
|
||||
const auto nodeLowerBound=[&](const FCavernRuntimeFeature::FSegmentNode& node)
|
||||
{
|
||||
double gapSquared=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double scale=axis==2?height:radius;
|
||||
const double q=point[axis]/scale;
|
||||
const double low=node.minimum[axis]/scale;
|
||||
const double high=node.maximum[axis]/scale;
|
||||
const double q=normalizedPoint[axis];
|
||||
const double low=node.minimumNormalized[axis];
|
||||
const double high=node.maximumNormalized[axis];
|
||||
const double gap=q<low?low-q:q>high?q-high:0.0;
|
||||
gapSquared+=gap*gap;
|
||||
}
|
||||
@@ -7696,52 +7943,67 @@ private:
|
||||
evaluateSegment(segment);
|
||||
const double candidateSupportDepth=
|
||||
feature.edge->floorSupportDepthMicrometres/1000000.0;
|
||||
if(candidateSupportDepth>0.0&&nearestRadial<radius-0.12
|
||||
constexpr double MinimumBackingDepthMetres=0.65;
|
||||
constexpr double FloorSupportFootprintRadiusMetres=0.95;
|
||||
// Portal edges join a wider authored passage whose floor spans the
|
||||
// full bore. Carry support across that shared mouth footprint;
|
||||
// ordinary backbone and branch edges retain the narrow support
|
||||
// strip that prevents nearby routes from sealing one another.
|
||||
const bool portalMouthEdge=feature.edge->startAnchorStableId.ends_with(
|
||||
"-mouth");
|
||||
const double floorSupportRadius=portalMouthEdge?radius:
|
||||
std::min(radius,FloorSupportFootprintRadiusMetres);
|
||||
const double candidateNormalDepth=(nearestFloor-point[2])
|
||||
/std::sqrt(1.0+nearestGrade*nearestGrade);
|
||||
if(candidateSupportDepth>=MinimumBackingDepthMetres
|
||||
&&candidateNormalDepth>=MinimumBackingDepthMetres
|
||||
&&candidateNormalDepth<=candidateSupportDepth
|
||||
&&nearestRadial<floorSupportRadius-0.12
|
||||
&&std::abs(point[2]-nearestFloor)<=height)
|
||||
{
|
||||
const double deltaZ=point[2]-nearestFloor;
|
||||
const double candidateScore=nearestRadial*nearestRadial+deltaZ*deltaZ;
|
||||
if(!feature.edge->floorProfileStableId.empty())
|
||||
const double candidateScore=nearestRadial*nearestRadial;
|
||||
const bool hasProfile=!feature.edge->floorProfileStableId.empty();
|
||||
const bool sameProfile=floorSupportProfileId&&hasProfile
|
||||
&&*floorSupportProfileId==feature.edge->floorProfileStableId;
|
||||
if(!floorSupportProfileId||sameProfile)
|
||||
{
|
||||
const bool sameProfile=floorSupportProfileId
|
||||
&&*floorSupportProfileId==feature.edge->floorProfileStableId;
|
||||
if(!floorSupportProfileId||sameProfile)
|
||||
{
|
||||
// Backbone edges share h(s). At a geometric joint their
|
||||
// cave volumes overlap, so the backing top is the lower
|
||||
// of the local floors, never the closer higher segment.
|
||||
if(!floorSupportProfileId||nearestFloor<floorSupportFloor
|
||||
||(nearestFloor==floorSupportFloor
|
||||
&&feature.edge->stableId<*floorSupportEdgeId))
|
||||
{
|
||||
floorSupportScore=candidateScore;
|
||||
floorSupportDepth=candidateSupportDepth;
|
||||
floorSupportFloor=nearestFloor;
|
||||
floorSupportGrade=nearestGrade;
|
||||
floorSupportEdgeId=&feature.edge->stableId;
|
||||
}
|
||||
floorSupportProfileId=&feature.edge->floorProfileStableId;
|
||||
}
|
||||
}
|
||||
else if(!floorSupportProfileId
|
||||
&&(candidateScore<floorSupportScore
|
||||
// For a shared backbone, support follows the closest
|
||||
// horizontal centerline. Vertical separation must not make
|
||||
// a nearby branch take support away from this path.
|
||||
if(!floorSupportEdgeId||candidateScore<floorSupportScore
|
||||
||(candidateScore==floorSupportScore
|
||||
&&(!floorSupportEdgeId
|
||||
||feature.edge->stableId<*floorSupportEdgeId))))
|
||||
{
|
||||
floorSupportScore=candidateScore;
|
||||
floorSupportDepth=candidateSupportDepth;
|
||||
floorSupportFloor=nearestFloor;
|
||||
floorSupportGrade=nearestGrade;
|
||||
floorSupportEdgeId=&feature.edge->stableId;
|
||||
&&(nearestFloor<floorSupportFloor
|
||||
||(nearestFloor==floorSupportFloor
|
||||
&&feature.edge->stableId<*floorSupportEdgeId))))
|
||||
{
|
||||
floorSupportScore=candidateScore;
|
||||
floorSupportDepth=candidateSupportDepth;
|
||||
floorSupportFloor=nearestFloor;
|
||||
floorSupportGrade=nearestGrade;
|
||||
floorSupportEdgeId=&feature.edge->stableId;
|
||||
}
|
||||
if(hasProfile)
|
||||
floorSupportProfileId=&feature.edge->floorProfileStableId;
|
||||
}
|
||||
}
|
||||
edgeFloor=nearestFloor;
|
||||
const double floor=(nearestFloor-point[2])
|
||||
/std::sqrt(1.0+nearestGrade*nearestGrade);
|
||||
const double width=feature.edge->smoothWidthMicrometres/1000000.0;
|
||||
featureVoid=SmoothMaxMetres(tube,floor,width);
|
||||
}
|
||||
if(!std::isfinite(featureVoid))continue;
|
||||
if(feature.edge&&feature.edge->floorSupportDepthMicrometres>=650000
|
||||
&&featureVoid<0.0)
|
||||
{
|
||||
if(edgeFloor<lowestOpenEdgeFloor
|
||||
||(edgeFloor==lowestOpenEdgeFloor&&feature.edge->floorProfileStableId
|
||||
<(lowestOpenEdgeProfileId?*lowestOpenEdgeProfileId:std::string{})))
|
||||
{
|
||||
lowestOpenEdgeFloor=edgeFloor;
|
||||
lowestOpenEdgeProfileId=&feature.edge->floorProfileStableId;
|
||||
}
|
||||
}
|
||||
if(!std::isfinite(caveVoid))caveVoid=featureVoid;
|
||||
else caveVoid=SmoothMinMetres(caveVoid,featureVoid,
|
||||
feature.room
|
||||
@@ -7752,8 +8014,10 @@ private:
|
||||
?feature.edge->unionWidthMicrometres
|
||||
:feature.edge->smoothWidthMicrometres)/1000000.0);
|
||||
}
|
||||
const double floorSupportNormalDepth=(floorSupportFloor-point[2])
|
||||
/std::sqrt(1.0+floorSupportGrade*floorSupportGrade);
|
||||
if(std::isfinite(floorSupportScore)&&point[2]<floorSupportFloor
|
||||
&&floorSupportFloor-point[2]<=floorSupportDepth)
|
||||
&&floorSupportNormalDepth<=floorSupportDepth)
|
||||
nativeFloorSupportMinimum=(point[2]-floorSupportFloor)
|
||||
/std::sqrt(1.0+floorSupportGrade*floorSupportGrade);
|
||||
// A versioned native caves recipe owns a solid host-rock field. The
|
||||
@@ -7767,7 +8031,15 @@ private:
|
||||
// Preserve each native graph edge's planned floor backing after smooth
|
||||
// unions. The record's finite depth keeps independent routes from sealing
|
||||
// one another when they pass at different elevations.
|
||||
if(std::isfinite(nativeFloorSupportMinimum))
|
||||
// Backing from a higher route edge must not seal a lower overlapping bore.
|
||||
// Room voids and same-level route joins still retain their authored support.
|
||||
const bool sameContinuousFloorProfile=floorSupportProfileId
|
||||
&&lowestOpenEdgeProfileId&&!floorSupportProfileId->empty()
|
||||
&&*floorSupportProfileId==*lowestOpenEdgeProfileId;
|
||||
const bool sealsOpenLowerEdge=std::isfinite(lowestOpenEdgeFloor)
|
||||
&&lowestOpenEdgeFloor<floorSupportFloor-0.50
|
||||
&&point[2]>lowestOpenEdgeFloor&&!sameContinuousFloorProfile;
|
||||
if(std::isfinite(nativeFloorSupportMinimum)&&!sealsOpenLowerEdge)
|
||||
composed=std::min(composed,nativeFloorSupportMinimum);
|
||||
if(nativeFloorSupport&&std::isfinite(nativeFloorSupportMinimum))
|
||||
*nativeFloorSupport=static_cast<float>(nativeFloorSupportMinimum/metresPerVoxel);
|
||||
@@ -9748,6 +10020,32 @@ private:
|
||||
},&Timing,nullptr,16);
|
||||
}
|
||||
|
||||
uint64 ApplyCriticalAirPackedSigns()
|
||||
{
|
||||
const int32 NX=Size[0],NY=Size[1];
|
||||
std::array<uint64,16> workerVoxels{};
|
||||
ParallelLines(static_cast<size_t>(NX)*NY,[&](size_t Line,int32 Worker)
|
||||
{
|
||||
const int32 X=Min[0]+static_cast<int32>(Line%NX);
|
||||
const int32 Y=Min[1]+static_cast<int32>(Line/NX);
|
||||
const size_t Base=Line*WordsPerZ;
|
||||
for(const uint32 Id:GetLineCandidates(2,X,Y))
|
||||
{
|
||||
const FPrimitive& primitive=Primitives[Id];
|
||||
if(!primitive.bCarve||!primitive.bCritical)continue;
|
||||
const int32 lo=std::max(primitive.Min[2],Min[2]);
|
||||
const int32 hi=std::min(primitive.Max[2],Max[2]);
|
||||
for(int32 Z=lo;Z<=hi;++Z)
|
||||
{
|
||||
if(!ShapeContains(primitive,X,Y,Z))continue;
|
||||
FillBits(RockWords,Base,Z-Min[2],Z-Min[2],false);
|
||||
++workerVoxels[static_cast<size_t>(Worker)];
|
||||
}
|
||||
}
|
||||
},nullptr,nullptr,16);
|
||||
return std::accumulate(workerVoxels.begin(),workerVoxels.end(),uint64{0});
|
||||
}
|
||||
|
||||
void BuildCandidateBands(FParallelTiming& AxisTiming,FParallelTiming& MergeTiming,
|
||||
double& MergeSeconds)
|
||||
{
|
||||
@@ -11491,7 +11789,8 @@ private:
|
||||
const int32 VZ=static_cast<int32>(std::lround(Z));
|
||||
const bool bCritical=IsInsideCriticalAir(VX,VY,VZ);
|
||||
if(bCritical){++RouteCritical;++SegmentCritical;}
|
||||
if(IsRock(VX,VY,VZ))
|
||||
const bool bRock=IsRock(VX,VY,VZ);
|
||||
if(bRock)
|
||||
{
|
||||
++Stats.WalkFailures;++RouteFailures;++SegmentFailures;
|
||||
if(RouteFailures==1)
|
||||
@@ -11705,7 +12004,7 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
|
||||
?shape_->ResolvedLayout->coreVoxelSizeMicrometres:250000);
|
||||
const double shellThickness=VF_PassageShellThicknessMetres*qPerMetre;
|
||||
const double stampedOutsideDistance=sweep->influenceSubvoxels+1.0;
|
||||
constexpr double MouthShellBlendMetres=5.0;
|
||||
constexpr double DefaultMouthShellBlendMetres=5.0;
|
||||
const double wallReliefShell=0.41*qPerMetre;
|
||||
const double floorDetailShell=1.0*qPerMetre;
|
||||
if(profile)
|
||||
@@ -11730,6 +12029,19 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
|
||||
{
|
||||
const auto& candidates=passageRouteSegments_[passageIndex];
|
||||
if(candidates.empty())continue;
|
||||
double mouthShellBlendMetres=DefaultMouthShellBlendMetres;
|
||||
if(shape_->ResolvedLayout&&passageIndex<shape_->ResolvedLayout->passages.size())
|
||||
{
|
||||
const auto& passage=shape_->ResolvedLayout->passages[passageIndex];
|
||||
const auto lower=std::find_if(shape_->ResolvedLayout->strates.begin(),
|
||||
shape_->ResolvedLayout->strates.end(),[&](const auto& stratum)
|
||||
{return stratum.strateIndex==passage.lowerStrateIndex;});
|
||||
if(lower!=shape_->ResolvedLayout->strates.end()
|
||||
&&lower->generatorKind=="TunnelNetwork"
|
||||
&&lower->recipeId=="Core.Caves.PrimitiveField"
|
||||
&&lower->recipeVersion==1)
|
||||
mouthShellBlendMetres=2.0;
|
||||
}
|
||||
const std::int32_t sweepIndex=static_cast<std::int32_t>(sweep->passages.size());
|
||||
sweep->passageToSweep[passageIndex]=sweepIndex;
|
||||
auto& passageSamples=sweep->passages.emplace_back();
|
||||
@@ -11883,7 +12195,7 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
|
||||
const double mouthDistanceMetres=std::max(0.0,std::min(alongMetres,
|
||||
constants.totalPathLengthMetres-alongMetres));
|
||||
const double shellFade=std::clamp(mouthDistanceMetres/
|
||||
MouthShellBlendMetres,0.0,1.0);
|
||||
mouthShellBlendMetres,0.0,1.0);
|
||||
const double smoothShellFade=shellFade*shellFade
|
||||
*(3.0-2.0*shellFade);
|
||||
const double floorDistanceMetres=(floorCenterZ-floorDepth-qz)/qPerMetre;
|
||||
@@ -11900,7 +12212,7 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
|
||||
const double pastMouthMetres=std::max(0.0,alongMetres
|
||||
-constants.totalPathLengthMetres);
|
||||
const double extensionFade=std::clamp(1.0
|
||||
-pastMouthMetres/MouthShellBlendMetres,0.0,1.0);
|
||||
-pastMouthMetres/mouthShellBlendMetres,0.0,1.0);
|
||||
floorExtensionFade=extensionFade*extensionFade
|
||||
*(3.0-2.0*extensionFade);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user