wip(L26, side branch): every passage style walkable in both recipes (0/0/0); SW bridges look wrong
All 4 styles x both recipes pass capsule, open-air and naturalness audits, and full-view hashes are deterministic. But the SW passage crosses 355 m of open SurfaceWorld sky as a ~2 km fragmented rock ramp, which is not natural. L27 replaces long bridges with a shared ceiling-to-ground rock column. 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
ef789aaa00
commit
37a223046c
13 files changed
+878
-41
No files matched your search
@@ -41,6 +41,27 @@ public:
|
||||
double SealThickness=0.0;
|
||||
};
|
||||
|
||||
const std::vector<FStandalonePassageBridgeRange>& GetPassageBridgeRanges() const
|
||||
{return PassageBridgeRanges;}
|
||||
|
||||
bool IsResolvedPassageBridgeSample(std::size_t PassageIndex,std::size_t SegmentIndex,
|
||||
const VoxelForgeCore::Vec3d& Position) const
|
||||
{
|
||||
if(IsPassageBridgeSegment(PassageIndex,SegmentIndex)
|
||||
&&PassageBridgeSignedDistance(PassageIndex,SegmentIndex,
|
||||
static_cast<float>(Position.x),static_cast<float>(Position.y),
|
||||
static_cast<float>(Position.z))<=0.0)return true;
|
||||
for(const auto& range:PassageBridgeRanges)
|
||||
{
|
||||
if(range.passageIndex!=PassageIndex)continue;
|
||||
for(std::size_t segment=range.firstSegment;segment<=range.lastSegment;++segment)
|
||||
if(segment!=SegmentIndex&&PassageBridgeSignedDistance(PassageIndex,segment,
|
||||
static_cast<float>(Position.x),static_cast<float>(Position.y),
|
||||
static_cast<float>(Position.z))<=0.0)return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
double GetCoreVoxelSizeCm() const
|
||||
{
|
||||
return Owner == nullptr || Owner->CoreVoxelSizeMicrons == 0
|
||||
@@ -168,20 +189,19 @@ public:
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
composeRouteMouthSupport(passage.upperStrateIndex,
|
||||
passage.upperMouthSubvoxels,true);
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||
{
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
if(passage.controlPoints.size()<2)continue;
|
||||
const double placementRadiusQ=static_cast<double>(passage.placementRadiusSubvoxels)
|
||||
+static_cast<double>(passage.midRadiusSubvoxels);
|
||||
if(qx*qx+qy*qy>placementRadiusQ*placementRadiusQ)
|
||||
continue;
|
||||
const double firstFloorQ=passage.controlPoints.front().positionSubvoxels[2]
|
||||
-passage.controlPoints.front().radiusSubvoxels+playerCenterAboveFloorQ;
|
||||
const double lastFloorQ=passage.controlPoints.back().positionSubvoxels[2]
|
||||
-passage.controlPoints.back().radiusSubvoxels+playerCenterAboveFloorQ;
|
||||
if(qz>firstFloorQ+2.0*passage.mouthRadiusSubvoxels
|
||||
||qz<lastFloorQ-2.0*passage.mouthRadiusSubvoxels)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const auto floorProfile=[&](const auto& point)
|
||||
{return point.positionSubvoxels[2]-point.radiusSubvoxels+playerCenterAboveFloorQ;};
|
||||
const std::ptrdiff_t lastIndex=static_cast<std::ptrdiff_t>(passage.controlPoints.size())-1;
|
||||
@@ -204,8 +224,10 @@ public:
|
||||
const std::ptrdiff_t carveEnd=std::min<std::ptrdiff_t>(lowerMouthIndex-1,
|
||||
(carveLow-passage.controlPoints.begin())+1);
|
||||
double bestSignedDistance=std::numeric_limits<double>::infinity();
|
||||
double bestPlayerCapsuleSignedDistance=std::numeric_limits<double>::infinity();
|
||||
double bestT=0.0;
|
||||
std::ptrdiff_t bestIndex=0;
|
||||
std::ptrdiff_t bestSupportSegmentIndex=0;
|
||||
bool hasSupportFloor=false;
|
||||
double bestSupportDensity=0.0;
|
||||
const auto considerCarveSegment=[&](std::ptrdiff_t index)
|
||||
@@ -225,12 +247,37 @@ public:
|
||||
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
|
||||
const double radius=static_cast<double>(a.radiusSubvoxels)
|
||||
+t*(b.radiusSubvoxels-a.radiusSubvoxels);
|
||||
const double horizontalLengthSq=dx*dx+dy*dy;
|
||||
const double playerT=horizontalLengthSq>0.0
|
||||
?std::clamp(((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):t;
|
||||
const double playerDx=qx-(ax+playerT*dx);
|
||||
const double playerDy=qy-(ay+playerT*dy);
|
||||
const double playerRadius=static_cast<double>(a.radiusSubvoxels)
|
||||
+playerT*(b.radiusSubvoxels-a.radiusSubvoxels);
|
||||
const double playerCenterZ=az+playerT*dz-playerRadius+playerCenterAboveFloorQ;
|
||||
const double playerVertical=std::max(0.0,
|
||||
std::abs(qz-playerCenterZ)-0.54*qPerMetre);
|
||||
const double playerCapsuleDistance=DeterministicSqrt(
|
||||
playerDx*playerDx+playerDy*playerDy+playerVertical*playerVertical)
|
||||
-0.34*qPerMetre;
|
||||
bestPlayerCapsuleSignedDistance=std::min(bestPlayerCapsuleSignedDistance,
|
||||
playerCapsuleDistance);
|
||||
const double signedDistance=DeterministicSqrt(ex*ex+ey*ey+ez*ez)-radius;
|
||||
if(signedDistance<bestSignedDistance)
|
||||
{bestSignedDistance=signedDistance;bestT=t;bestIndex=index;}
|
||||
};
|
||||
for(std::ptrdiff_t index=carveBegin;index<=carveEnd;++index)
|
||||
considerCarveSegment(index);
|
||||
// Alternate styles can bend locally at either mouth, so floorProfile is
|
||||
// not monotone over those short endpoint runs. Keep the binary search for
|
||||
// the long descent and inspect both endpoint neighborhoods directly.
|
||||
for(std::ptrdiff_t index=0;index<std::min<std::ptrdiff_t>(8,lowerMouthIndex);++index)
|
||||
considerCarveSegment(index);
|
||||
const std::ptrdiff_t mouthSearchBegin=std::max<std::ptrdiff_t>(0,lowerMouthIndex-8);
|
||||
const std::ptrdiff_t mouthSearchEnd=std::min<std::ptrdiff_t>(lastIndex,
|
||||
lowerMouthIndex+8);
|
||||
for(std::ptrdiff_t index=mouthSearchBegin;index<mouthSearchEnd;++index)
|
||||
considerCarveSegment(index);
|
||||
// The route-bound landing can rise and fall after the lower mouth. Scan
|
||||
// that short tail directly; binary search is only valid on the descent.
|
||||
for(std::ptrdiff_t index=lowerMouthIndex;index<lastIndex;++index)
|
||||
@@ -250,6 +297,12 @@ public:
|
||||
const double wallReliefWeight=1.0-0.80*verticalWallAlignment;
|
||||
const double roughSignedDistance=bestSignedDistance
|
||||
+static_cast<double>(std::clamp(relief,-1.0f,1.0f))*0.24*qPerMetre*wallReliefWeight;
|
||||
if(IsPassageBridgeSegment(passageIndex,static_cast<std::size_t>(bestIndex)))
|
||||
{
|
||||
const double bridgeDistance=PassageBridgeSignedDistance(passageIndex,
|
||||
static_cast<std::size_t>(bestIndex),X,Y,Z);
|
||||
composed=std::min(composed,static_cast<float>(bridgeDistance/Q));
|
||||
}
|
||||
// Positive density is air; rough rock is cut with full side/ceiling detail and
|
||||
// gentler relief on the gravity-facing wall so the supported tread stays open.
|
||||
composed=std::max(composed,static_cast<float>(-roughSignedDistance/Q));
|
||||
@@ -264,7 +317,10 @@ public:
|
||||
const double radiusB=segmentB.radiusSubvoxels;
|
||||
// Keep the tread within the probe band and below the capsule's
|
||||
// lowest sampled point.
|
||||
const double floorOffset=0.20;
|
||||
const double floorOffset=IsPassageBridgeSegment(passageIndex,
|
||||
static_cast<std::size_t>(index))
|
||||
?PassageBridgeDeckOffsetMetres(passageIndex,static_cast<std::size_t>(index))
|
||||
:0.20;
|
||||
const double horizontalLengthSq=dx*dx+dy*dy;
|
||||
const double supportT=horizontalLengthSq>0.0
|
||||
?std::clamp(((qx-segmentA.positionSubvoxels[0])*dx
|
||||
@@ -304,6 +360,7 @@ public:
|
||||
if(supportDistanceSq>=bestSupportDistanceSquared)return;
|
||||
bestSupportDistanceSquared=supportDistanceSq;
|
||||
hasSupportFloor=true;
|
||||
bestSupportSegmentIndex=index;
|
||||
bestSupportDensity=std::clamp(Z-floorVoxels,
|
||||
-supportThicknessVoxels,0.0);
|
||||
};
|
||||
@@ -330,7 +387,11 @@ public:
|
||||
considerSupportSegment(index);
|
||||
// Keep route treads inside the native rock envelope. Carving may reopen the
|
||||
// floor above this point, but a support cannot create a new surface in air.
|
||||
if(hasSupportFloor&&density<0.0f)
|
||||
const bool bridgeFloorSupport=hasSupportFloor
|
||||
&&IsPassageBridgeSegment(passageIndex,
|
||||
static_cast<std::size_t>(bestSupportSegmentIndex));
|
||||
if(hasSupportFloor&&bestPlayerCapsuleSignedDistance>=0.05*qPerMetre
|
||||
&&(density<0.0f||bridgeFloorSupport))
|
||||
composed=std::min(composed,static_cast<float>(bestSupportDensity));
|
||||
}
|
||||
return composed;
|
||||
@@ -343,6 +404,10 @@ public:
|
||||
{
|
||||
if(!ResolvedLayout||ResolvedLayout->passages.empty())return false;
|
||||
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
const double voxelsPerMetre=Owner&&Owner->CoreVoxelSizeMicrons>0
|
||||
?1000000.0/Owner->CoreVoxelSizeMicrons:4.0;
|
||||
const double bridgeHalfWidthVoxels=3.5*voxelsPerMetre;
|
||||
const double bridgeFootDepthVoxels=(5.0-0.13)*voxelsPerMetre;
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
for(std::size_t i=1;i<passage.controlPoints.size();++i)
|
||||
@@ -351,13 +416,18 @@ public:
|
||||
const double radius=static_cast<double>(std::max(a.radiusSubvoxels,b.radiusSubvoxels))/Q;
|
||||
const double floorThicknessVoxels=Owner&&Owner->CoreVoxelSizeMicrons>0
|
||||
?1000000.0/Owner->CoreVoxelSizeMicrons:4.0;
|
||||
// A resolved passage bridge extends to a 3.5 m half-width and almost 5 m
|
||||
// below the bore floor. Keep those cells shape-dependent for classification
|
||||
// and streaming even before its bridge ranges have been compiled.
|
||||
const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels);
|
||||
const double footDepth=std::max(floorThicknessVoxels,bridgeFootDepthVoxels);
|
||||
const double low[3]={
|
||||
std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-radius,
|
||||
std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-radius,
|
||||
std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius-floorThicknessVoxels};
|
||||
std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-horizontalExtent,
|
||||
std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-horizontalExtent,
|
||||
std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius-footDepth};
|
||||
const double high[3]={
|
||||
std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+radius,
|
||||
std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+radius,
|
||||
std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+horizontalExtent,
|
||||
std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+horizontalExtent,
|
||||
std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius};
|
||||
bool overlap=true;
|
||||
for(int axis=0;axis<3;++axis)
|
||||
@@ -715,6 +785,7 @@ public:
|
||||
if(RoundTunnelsEnabled)
|
||||
BuildRoundTunnelPrimitives();
|
||||
BuildSpatialBins(LineBinSize);
|
||||
BuildPassageBridgePlan();
|
||||
Stats.LocalLineBinSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
|
||||
Stats.ShapeSeconds=std::chrono::duration<double>(Clock::now()-Stage).count();
|
||||
Stats.PrimitiveCount=Primitives.size();
|
||||
@@ -1622,6 +1693,218 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsPassageBridgeSegment(std::size_t PassageIndex,std::size_t SegmentIndex) const
|
||||
{
|
||||
return std::any_of(PassageBridgeRanges.begin(),PassageBridgeRanges.end(),
|
||||
[PassageIndex,SegmentIndex](const auto& range)
|
||||
{return range.passageIndex==PassageIndex
|
||||
&&SegmentIndex>=range.firstSegment&&SegmentIndex<=range.lastSegment;});
|
||||
}
|
||||
|
||||
double PassageBridgeDeckOffsetMetres(std::size_t PassageIndex,
|
||||
std::size_t SegmentIndex) const
|
||||
{
|
||||
for(const auto& range:PassageBridgeRanges)
|
||||
{
|
||||
if(range.passageIndex!=PassageIndex||SegmentIndex>range.lastSegment)continue;
|
||||
if(SegmentIndex>=range.firstSegment)return 0.13;
|
||||
}
|
||||
return 0.20;
|
||||
}
|
||||
|
||||
double PassageBridgeSignedDistance(std::size_t PassageIndex,std::size_t SegmentIndex,
|
||||
float X,float Y,float Z) const
|
||||
{
|
||||
if(!ResolvedLayout||PassageIndex>=ResolvedLayout->passages.size())
|
||||
return std::numeric_limits<double>::infinity();
|
||||
const auto& passage=ResolvedLayout->passages[PassageIndex];
|
||||
if(SegmentIndex+1>=passage.controlPoints.size())
|
||||
return std::numeric_limits<double>::infinity();
|
||||
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
const double qPerMetre=Q*1000000.0/std::max<std::uint32_t>(1,
|
||||
ResolvedLayout->coreVoxelSizeMicrometres);
|
||||
const auto& a=passage.controlPoints[SegmentIndex];
|
||||
const auto& b=passage.controlPoints[SegmentIndex+1];
|
||||
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;
|
||||
const double dy=static_cast<double>(b.positionSubvoxels[1])-ay;
|
||||
const double horizontalLengthSq=dx*dx+dy*dy;
|
||||
const double qx=static_cast<double>(X)*Q,qy=static_cast<double>(Y)*Q;
|
||||
const double qz=static_cast<double>(Z)*Q;
|
||||
const double t=horizontalLengthSq>0.0
|
||||
?std::clamp(((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):0.0;
|
||||
const double centerX=ax+t*dx,centerY=ay+t*dy;
|
||||
const double centerZ=a.positionSubvoxels[2]+t*(
|
||||
static_cast<double>(b.positionSubvoxels[2])-a.positionSubvoxels[2]);
|
||||
const double radius=a.radiusSubvoxels+t*(
|
||||
static_cast<double>(b.radiusSubvoxels)-a.radiusSubvoxels);
|
||||
// Embed the deck 13 cm above the bore bottom: within the 25 cm support
|
||||
// probe, while leaving 16 cm between it and the capsule's lowest sample.
|
||||
const double floorQ=centerZ-radius
|
||||
+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 thickness=(4.0+1.0*relief)*qPerMetre;
|
||||
const double horizontalDistance=DeterministicSqrt((qx-centerX)*(qx-centerX)
|
||||
+(qy-centerY)*(qy-centerY));
|
||||
const double sideDistance=horizontalDistance-halfWidth;
|
||||
const double topDistance=qz-floorQ;
|
||||
const double bottomDistance=floorQ-thickness-qz;
|
||||
const double verticalDistance=std::max(topDistance,bottomDistance);
|
||||
const double outside=DeterministicSqrt(
|
||||
std::max(sideDistance,0.0)*std::max(sideDistance,0.0)
|
||||
+std::max(verticalDistance,0.0)*std::max(verticalDistance,0.0));
|
||||
const double inside=std::min(std::max(sideDistance,verticalDistance),0.0);
|
||||
return outside+inside;
|
||||
}
|
||||
|
||||
float NativePassageDensityAt(float X,float Y,float Z) const
|
||||
{
|
||||
float density=0.0f;
|
||||
if(!NativeFloatingIslandDensityAt(X,Y,Z,density)
|
||||
&&!NativeSurfaceDensityAt(X,Y,Z,density))
|
||||
{
|
||||
if(UsesLegacySurfaceField(X,Y,Z)&&Owner&&Owner->Generator)
|
||||
density=Owner->Generator->GetDensityAt(X,Y,Z);
|
||||
else
|
||||
density=AnalyticDensityAt(X,Y,Z,true,nullptr,nullptr,nullptr,
|
||||
false,false);
|
||||
}
|
||||
return ApplyResolvedStrateBoundaryBand(density,X,Y,Z);
|
||||
}
|
||||
|
||||
void BuildPassageBridgePlan()
|
||||
{
|
||||
PassageBridgeRanges.clear();
|
||||
if(!Owner||!ResolvedLayout||ResolvedLayout->passages.empty()
|
||||
||!Owner->PassageBridgeCache)return;
|
||||
auto& cache=*Owner->PassageBridgeCache;
|
||||
std::lock_guard<std::mutex> lock(cache.mutex);
|
||||
const auto cached=cache.byLayout.find(ResolvedLayout->stableIdentity);
|
||||
if(cached!=cache.byLayout.end())
|
||||
{PassageBridgeRanges=cached->second;return;}
|
||||
|
||||
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
const double qPerMetre=Q*1000000.0/std::max<std::uint32_t>(1,
|
||||
ResolvedLayout->coreVoxelSizeMicrometres);
|
||||
const auto inSurfaceWorldStratum=[&](double zSubvoxels)
|
||||
{
|
||||
const double zMicrometres=zSubvoxels
|
||||
*ResolvedLayout->coreVoxelSizeMicrometres/Q;
|
||||
return std::any_of(ResolvedLayout->strates.begin(),ResolvedLayout->strates.end(),
|
||||
[zMicrometres](const auto& stratum)
|
||||
{return stratum.generatorKind=="SurfaceWorld"
|
||||
&&zMicrometres>=stratum.interiorBottomMicrometres
|
||||
&&zMicrometres<stratum.interiorTopMicrometres;});
|
||||
};
|
||||
constexpr std::array<std::array<double,2>,5> supportOffsets{{
|
||||
{{0.0,0.0}},{{0.45,0.0}},{{-0.45,0.0}},{{0.0,0.45}},{{0.0,-0.45}}}};
|
||||
constexpr std::array<double,2> supportDepthsMetres{{0.125,0.25}};
|
||||
struct FPathSample
|
||||
{
|
||||
std::size_t segment=0;
|
||||
std::array<double,4> point{};
|
||||
bool nativeSupported=false;
|
||||
};
|
||||
const auto nativeSupportedAt=[&](const std::array<double,4>& point)
|
||||
{
|
||||
const double floorQ=point[2]-point[3]+0.13*qPerMetre;
|
||||
std::uint32_t supported=0;
|
||||
for(const auto& offset:supportOffsets)
|
||||
{
|
||||
bool hasSolid=false;
|
||||
for(const double depth:supportDepthsMetres)
|
||||
{
|
||||
const double sampleZ=floorQ-depth*qPerMetre;
|
||||
// Bridge spans address the measured SurfaceWorld void. Other native
|
||||
// strata retain their own route/cave composition and are excluded here.
|
||||
if(!inSurfaceWorldStratum(sampleZ)){hasSolid=true;break;}
|
||||
float density=1.0f;
|
||||
if(!NativeSurfaceDensityAt(
|
||||
static_cast<float>((point[0]+offset[0]*qPerMetre)/Q),
|
||||
static_cast<float>((point[1]+offset[1]*qPerMetre)/Q),
|
||||
static_cast<float>(sampleZ/Q),density))continue;
|
||||
if(density<0.0f){hasSolid=true;break;}
|
||||
}
|
||||
supported+=hasSolid;
|
||||
}
|
||||
return supported>=3;
|
||||
};
|
||||
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
|
||||
++passageIndex)
|
||||
{
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
if(passage.controlPoints.size()<2)continue;
|
||||
std::vector<FPathSample> samples;
|
||||
samples.reserve(passage.controlPoints.size()*2);
|
||||
const auto pointAt=[&](std::size_t segment,double t)
|
||||
{
|
||||
const auto& a=passage.controlPoints[segment];
|
||||
const auto& b=passage.controlPoints[segment+1];
|
||||
FPathSample sample;sample.segment=segment;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
sample.point[axis]=a.positionSubvoxels[axis]+t*(
|
||||
static_cast<double>(b.positionSubvoxels[axis])
|
||||
-a.positionSubvoxels[axis]);
|
||||
sample.point[3]=a.radiusSubvoxels+t*(
|
||||
static_cast<double>(b.radiusSubvoxels)-a.radiusSubvoxels);
|
||||
sample.nativeSupported=nativeSupportedAt(sample.point);
|
||||
return sample;
|
||||
};
|
||||
samples.push_back(pointAt(0,0.0));
|
||||
for(std::size_t segment=0;segment+1<passage.controlPoints.size();++segment)
|
||||
{
|
||||
const auto& a=passage.controlPoints[segment];
|
||||
const auto& b=passage.controlPoints[segment+1];
|
||||
const double dx=static_cast<double>(b.positionSubvoxels[0])-a.positionSubvoxels[0];
|
||||
const double dy=static_cast<double>(b.positionSubvoxels[1])-a.positionSubvoxels[1];
|
||||
const double dz=static_cast<double>(b.positionSubvoxels[2])-a.positionSubvoxels[2];
|
||||
const double lengthMetres=DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre;
|
||||
const std::size_t intervals=std::max<std::size_t>(1,
|
||||
static_cast<std::size_t>(std::ceil(lengthMetres/0.5)));
|
||||
for(std::size_t step=1;step<=intervals;++step)
|
||||
samples.push_back(pointAt(segment,static_cast<double>(step)/intervals));
|
||||
}
|
||||
std::size_t cursor=0;
|
||||
while(cursor<samples.size())
|
||||
{
|
||||
if(samples[cursor].nativeSupported){++cursor;continue;}
|
||||
const std::size_t gapBegin=cursor;
|
||||
while(cursor<samples.size()&&!samples[cursor].nativeSupported)++cursor;
|
||||
const std::size_t gapEnd=cursor-1;
|
||||
if(gapBegin==0||cursor>=samples.size())continue;
|
||||
double gapLength=0.0;
|
||||
for(std::size_t index=gapBegin;index<=gapEnd;++index)
|
||||
{
|
||||
const auto& a=samples[index-1].point;
|
||||
const auto& b=samples[index].point;
|
||||
const double dx=b[0]-a[0],dy=b[1]-a[1],dz=b[2]-a[2];
|
||||
gapLength+=DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre;
|
||||
}
|
||||
if(gapLength<1.0)continue;
|
||||
FStandalonePassageBridgeRange range;
|
||||
range.passageIndex=passageIndex;
|
||||
range.firstSegment=samples[gapBegin-1].segment;
|
||||
range.lastSegment=samples[cursor].segment;
|
||||
range.unsupportedSamples=gapEnd-gapBegin+1;
|
||||
range.unsupportedLengthMetres=gapLength;
|
||||
range.startPoint=samples[gapBegin-1].point;
|
||||
range.endPoint=samples[cursor].point;
|
||||
PassageBridgeRanges.push_back(range);
|
||||
std::printf("[PassageRockBridgePlan] id=%s first_segment=%zu last_segment=%zu unsupported_samples=%llu gap_length_m=%.3f connected_start=1 connected_end=1 width_m=6.000 thickness_m=3.000..5.000 status=PLANNED\n",
|
||||
passage.stableId.c_str(),range.firstSegment,range.lastSegment,
|
||||
static_cast<unsigned long long>(range.unsupportedSamples),gapLength);
|
||||
}
|
||||
if(std::none_of(PassageBridgeRanges.begin(),PassageBridgeRanges.end(),
|
||||
[passageIndex](const auto& range){return range.passageIndex==passageIndex;}))
|
||||
std::printf("[PassageRockBridgePlan] id=%s spans=0 status=NOT_REQUIRED\n",
|
||||
passage.stableId.c_str());
|
||||
}
|
||||
cache.byLayout.emplace(ResolvedLayout->stableIdentity,PassageBridgeRanges);
|
||||
}
|
||||
|
||||
static uint64 PackLatticeKey(int32 X,int32 Y,int32 Z)
|
||||
{
|
||||
constexpr uint64 Mask=0x1FFFFFull;
|
||||
@@ -1656,17 +1939,19 @@ private:
|
||||
float* OutDetailDisplacement=nullptr,
|
||||
float* OutCaveInfluence=nullptr,
|
||||
float* OutFloorInfluence=nullptr,
|
||||
bool bApplyEdits=true) const
|
||||
bool bApplyEdits=true,bool bApplyComposition=true) const
|
||||
{
|
||||
if(SharedDensitySource)return SharedDensitySource->AnalyticDensityAt(X,Y,Z,
|
||||
bClampNearZero,OutDetailDisplacement,OutCaveInfluence,OutFloorInfluence,bApplyEdits);
|
||||
bClampNearZero,OutDetailDisplacement,OutCaveInfluence,OutFloorInfluence,
|
||||
bApplyEdits,bApplyComposition);
|
||||
if(OutDetailDisplacement)*OutDetailDisplacement=0.0f;
|
||||
if(OutCaveInfluence)*OutCaveInfluence=0.0f;
|
||||
if(OutFloorInfluence)*OutFloorInfluence=0.0f;
|
||||
float IslandDensity=0.0f;
|
||||
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
|
||||
{
|
||||
const float composed=ApplyResolvedStrateComposition(IslandDensity,X,Y,Z);
|
||||
const float composed=bApplyComposition
|
||||
?ApplyResolvedStrateComposition(IslandDensity,X,Y,Z):IslandDensity;
|
||||
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
||||
}
|
||||
if(UsesLegacySurfaceField(X,Y,Z))
|
||||
@@ -1674,11 +1959,13 @@ private:
|
||||
float NativeDensity=0.0f;
|
||||
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
||||
{
|
||||
NativeDensity=ApplyResolvedStrateComposition(NativeDensity,X,Y,Z);
|
||||
if(bApplyComposition)
|
||||
NativeDensity=ApplyResolvedStrateComposition(NativeDensity,X,Y,Z);
|
||||
return bApplyEdits?ApplyEditBrushes(NativeDensity,X,Y,Z):NativeDensity;
|
||||
}
|
||||
const float Density=Owner->Generator->GetDensityAt(X,Y,Z);
|
||||
const float composed=ApplyResolvedStrateComposition(Density,X,Y,Z);
|
||||
const float composed=bApplyComposition
|
||||
?ApplyResolvedStrateComposition(Density,X,Y,Z):Density;
|
||||
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
||||
}
|
||||
const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]
|
||||
@@ -1693,7 +1980,8 @@ private:
|
||||
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
|
||||
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
|
||||
bClampNearZero?0.05f:1.0e-7f);
|
||||
const float composed=ApplyResolvedStrateComposition(Density,X,Y,Z);
|
||||
const float composed=bApplyComposition
|
||||
?ApplyResolvedStrateComposition(Density,X,Y,Z):Density;
|
||||
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
||||
}
|
||||
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
|
||||
@@ -1723,7 +2011,7 @@ private:
|
||||
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
|
||||
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
|
||||
bClampNearZero?0.05f:1.0e-7f);
|
||||
Density=ApplyResolvedStrateComposition(Density,X,Y,Z);
|
||||
if(bApplyComposition)Density=ApplyResolvedStrateComposition(Density,X,Y,Z);
|
||||
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density;
|
||||
}
|
||||
|
||||
@@ -5863,6 +6151,7 @@ private:
|
||||
std::vector<FFarConnectorSegment> FarConnectorSegments;
|
||||
std::unordered_map<FBinKey,std::vector<uint32>,FBinHash> FarConnectorSpatialBins;
|
||||
std::vector<FEditBrush> EditBrushes;
|
||||
std::vector<FStandalonePassageBridgeRange> PassageBridgeRanges;
|
||||
std::array<FAxisBins,3> AxisBins;
|
||||
std::unordered_map<FBinKey,FBinItems,FBinHash> SpatialBins;
|
||||
std::unordered_map<FBinKey,FBinItems,FBinHash> CriticalAirBins;
|
||||
|
||||
@@ -1164,6 +1164,44 @@ namespace VoxelForgeCore
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ResolvedPassageTouchesBox(const ResolvedLayoutSnapshotV3& layout,
|
||||
const FIntVector& minimum,const FIntVector& maximum,std::uint32_t coreVoxelSizeMicrons)
|
||||
{
|
||||
if(layout.passages.empty())return false;
|
||||
constexpr double Q=static_cast<double>(WorldAssemblySubvoxelsPerVoxel);
|
||||
const double voxelsPerMetre=coreVoxelSizeMicrons>0
|
||||
?1000000.0/coreVoxelSizeMicrons:4.0;
|
||||
const double bridgeHalfWidthVoxels=3.5*voxelsPerMetre;
|
||||
const double bridgeFootDepthVoxels=(5.0-0.13)*voxelsPerMetre;
|
||||
const double tileMinimum[3]={static_cast<double>(minimum.X),
|
||||
static_cast<double>(minimum.Y),static_cast<double>(minimum.Z)};
|
||||
const double tileMaximum[3]={static_cast<double>(maximum.X),
|
||||
static_cast<double>(maximum.Y),static_cast<double>(maximum.Z)};
|
||||
for(const auto& passage:layout.passages)
|
||||
for(std::size_t segment=1;segment<passage.controlPoints.size();++segment)
|
||||
{
|
||||
const auto& a=passage.controlPoints[segment-1];
|
||||
const auto& b=passage.controlPoints[segment];
|
||||
const double radius=static_cast<double>(std::max(
|
||||
a.radiusSubvoxels,b.radiusSubvoxels))/Q;
|
||||
const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels);
|
||||
const double low[3]={
|
||||
std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-horizontalExtent,
|
||||
std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-horizontalExtent,
|
||||
std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius
|
||||
-bridgeFootDepthVoxels};
|
||||
const double high[3]={
|
||||
std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+horizontalExtent,
|
||||
std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+horizontalExtent,
|
||||
std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius};
|
||||
bool overlap=true;
|
||||
for(int axis=0;axis<3;++axis)
|
||||
overlap&=high[axis]>=tileMinimum[axis]&&low[axis]<=tileMaximum[axis];
|
||||
if(overlap)return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool World::compile(const ShapeBuildRequest& request,const JobScheduler& scheduler,std::string& error)
|
||||
@@ -1246,6 +1284,8 @@ namespace VoxelForgeCore
|
||||
if(IntersectsBox(feature.minimumSubvoxels,feature.maximumSubvoxels,
|
||||
subMinimum,subMaximum))return false;
|
||||
if(RouteTouchesBox(*selectedPlan,subMinimum,subMaximum))return false;
|
||||
if(ResolvedPassageTouchesBox(*impl_->resolvedLayout,boundsMin,boundsMax,
|
||||
impl_->owner->CoreVoxelSizeMicrons))return false;
|
||||
if(OwnerPassageTouchesBox(*impl_->owner,boundsMin,boundsMax))return false;
|
||||
FStandaloneShapeWorld proofGeometry;
|
||||
if(!proofGeometry.BuildShapeDependencyProof(*impl_->owner,boundsMin,boundsMax,
|
||||
@@ -1502,6 +1542,28 @@ namespace VoxelForgeCore
|
||||
|
||||
float World::densityAt(Vec3d position) const
|
||||
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; }
|
||||
std::vector<PassageBridgeRangeAuditV1> World::passageBridgeRanges() const
|
||||
{
|
||||
std::vector<PassageBridgeRangeAuditV1> result;
|
||||
if(!impl_||!impl_->shapeReady.load(std::memory_order_acquire)||!impl_->shape)return result;
|
||||
const auto& ranges=impl_->shape->GetPassageBridgeRanges();
|
||||
result.reserve(ranges.size());
|
||||
for(const auto& range:ranges)
|
||||
{
|
||||
PassageBridgeRangeAuditV1 value;
|
||||
value.passageIndex=range.passageIndex;value.firstSegment=range.firstSegment;
|
||||
value.lastSegment=range.lastSegment;value.unsupportedSamples=range.unsupportedSamples;
|
||||
value.unsupportedLengthMetres=range.unsupportedLengthMetres;
|
||||
value.startPointSubvoxels=range.startPoint;value.endPointSubvoxels=range.endPoint;
|
||||
result.push_back(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
bool World::isPassageBridgeSample(std::size_t passageIndex,std::size_t segmentIndex,
|
||||
Vec3d position) const
|
||||
{return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape
|
||||
&&impl_->shape->IsResolvedPassageBridgeSample(passageIndex,segmentIndex,
|
||||
{position.x,position.y,position.z});}
|
||||
std::string World::densityDispatchPathAt(Vec3d position) const
|
||||
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape
|
||||
?impl_->shape->DensityDispatchPathAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z))
|
||||
|
||||
Reference in new issue
Block a user