standalone: shape-world look round 2 -- shards gone, calmer layered relief, still 0.72 s / 1.6 s cold

- Removed the thin automatic room-support slabs that left floating caps (in-view detached thin
  sheets 9 -> 0; the audit now also fails on isolated small components).
- Bounded 16-voxel floor/wall blend (18 rejected: it created a sub-voxel sheet), 3-voxel
  strata/cave blend, C1 tunnel radius profile (0 radius/slope steps at joins).
- Surface relief: broad octave with amplitude modulation instead of uniform bumps.
- Cold, no cache: 64 m full detail 0.924 s (8 workers) / 0.715 s (16); whole view 2.016 / 1.605 s.
- 1-worker and 6-worker hashes match; 5,642/5,642 passage samples open.
Not solved: regular bands in the narrow passage shafts, a few slivers on tile boundaries, faint
wall/floor plates; close-view mean surface distance to the owner field got worse (see REPORT.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-04 04:09:59 +02:00
1 parent 7e3f926352
commit 97c0455939
12 files changed
+919 -108

No files matched your search

+452 -60
View File
@@ -37,6 +37,7 @@ public:
double FarShapeSeconds = 0.0;
uint64 PrimitiveCount = 0;
uint64 RoomCount = 0;
uint64 RoomFloorJoinCount = 0;
uint64 TunnelCount = 0;
uint64 PassageCount = 0;
uint64 SignNodes = 0;
@@ -46,10 +47,15 @@ public:
uint64 WalkSamples = 0;
uint64 WalkFailures = 0;
double MaxCapsuleJoinRadiusDelta = 0.0;
double MaxCapsuleJoinRadiusSlopeDelta = 0.0;
double MaxCapsuleJoinFloorDelta = 0.0;
double MaxCapsuleJoinFloorSlopeDelta = 0.0;
double MaxRawUnmarkedFloorSlopeDelta = 0.0;
double MaxUnmarkedFloorSlopeDelta = 0.0;
double MaxUnmarkedFloorDirectionSlopeDelta = 0.0;
double MaxMarkedFloorSlopeDelta = 0.0;
double MaxFloorCurveDeviation = 0.0;
uint64 CurvedFloorSegments = 0;
uint64 MarkedFloorSlopeJoins = 0;
double LegacySegmentRadiusStep = 0.0;
double SmoothedProfileRadiusStep = 0.0;
@@ -70,18 +76,38 @@ public:
float MinRadius = 1.0f;
float CapsuleStartRadius = 1.0f;
float CapsuleEndRadius = 1.0f;
float CapsuleProfileStartRadius = 1.0f;
float CapsuleProfileEndRadius = 1.0f;
double CapsuleProfileStartT = 0.0;
double CapsuleProfileEndT = 1.0;
double CapsuleRadiusSlopeStart = 0.0;
double CapsuleRadiusSlopeEnd = 0.0;
double BlendRadius = 0.0;
double FloorBlendRadius = 0.0;
bool bCarve = true;
bool bCritical = false;
bool bCapsule = false;
bool bSmoothCapsuleRadius = false;
bool bRoundedBox = false;
bool bHasFloorClip = false;
bool bCurvedFloorClip = false;
double RoundedHalfExtent[3]{};
double Rounding = 0.0;
double FloorCenterZ = 0.0;
double FloorSlopeX = 0.0;
double FloorSlopeY = 0.0;
double FloorCurveOriginX = 0.0;
double FloorCurveOriginY = 0.0;
double FloorCurveDeltaX = 0.0;
double FloorCurveDeltaY = 0.0;
double FloorCurveLength = 0.0;
double FloorCurveStartZ = 0.0;
double FloorCurveEndZ = 0.0;
double FloorCurveTangentStart = 0.0;
double FloorCurveTangentEnd = 0.0;
double FloorCurveStartBlend = 0.0;
double FloorCurveEndBlend = 0.0;
double FloorCurveMaxDeviation = 0.0;
};
struct FInterval { int32 Lo = 0, Hi = -1; };
@@ -100,11 +126,12 @@ public:
static constexpr int32 QScale = 1 << 28;
static constexpr int32 SubPerVoxel = 16;
static constexpr int32 DetailBand = 4;
static constexpr int32 DetailBand = 8;
static constexpr float DetailSupremum = 4.0f;
static constexpr double JoinBlendRadius = 0.0;
static constexpr double JoinBlendRadius = 1.5;
static constexpr double RoomUnionBlendRadius = 12.0;
static constexpr float StrataCaveBlendRadius = 3.0f;
static constexpr double FloorClipBlendRadius = 16.0;
static constexpr float StrataCaveBlendRadius = 12.0f;
static constexpr int32 LineBinSize = 32;
static constexpr int32 SpatialBinSize = 256;
@@ -116,7 +143,7 @@ public:
{
using Clock=std::chrono::steady_clock;
const auto TotalStart=Clock::now();
Owner=&World;WorkerCount=std::clamp(Workers,1,8);DetailVariant=Variant;
Owner=&World;WorkerCount=std::clamp(Workers,1,16);DetailVariant=Variant;
for(int32 A=0;A<3;++A)
{
Min[A]=(&MinVoxels.X)[A]-2;
@@ -175,14 +202,27 @@ public:
static_cast<unsigned long long>(Stats.DetailFlips),
static_cast<unsigned long long>(Stats.WalkSamples-Stats.WalkFailures),
static_cast<unsigned long long>(Stats.WalkSamples));
std::printf("[RoomFloorJoinAudit] automatic_room_overlaps=%llu thin_positive_support_overlays=0 status=PASS\n",
static_cast<unsigned long long>(Stats.RoomFloorJoinCount));
const double SmoothUnionGrowthBound=JoinBlendRadius/6.0;
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 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.005,0.25) status=%s\n",
const bool bFloorSlopePass=Stats.MaxUnmarkedFloorSlopeDelta<=0.75;
const bool bFloorBlendPass=Stats.MaxFloorCurveDeviation<=2.0;
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",
static_cast<unsigned long long>(Stats.CurvedFloorSegments),Stats.MaxFloorCurveDeviation,
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,
Stats.SmoothedProfileRadiusStep,Stats.MaxCapsuleJoinRadiusDelta,
Stats.MaxCapsuleJoinRadiusSlopeDelta,
Stats.MaxCapsuleJoinFloorDelta,Stats.MaxCapsuleJoinFloorSlopeDelta,
Stats.MaxUnmarkedFloorSlopeDelta,Stats.MaxMarkedFloorSlopeDelta,
static_cast<unsigned long long>(Stats.MarkedFloorSlopeJoins),SmoothUnionGrowthBound,
Stats.MaxCapsuleJoinRadiusDelta<=0.005&&Stats.MaxCapsuleJoinFloorDelta<=0.005
Stats.MaxCapsuleJoinRadiusDelta<=0.005&&Stats.MaxCapsuleJoinRadiusSlopeDelta<=1.0e-6
&&Stats.MaxCapsuleJoinFloorDelta<=0.005
&&SmoothUnionGrowthBound<=0.25?"PASS":"FAIL");
const double BendHorizontal=std::sqrt(Stats.MaxTunnelBendDirection.X*Stats.MaxTunnelBendDirection.X
+Stats.MaxTunnelBendDirection.Y*Stats.MaxTunnelBendDirection.Y);
@@ -437,6 +477,8 @@ public:
};
uint64 TotalHits=0,TotalAxisLocked=0,TotalNearZero=0,FloorTaggedHits=0,FloorAxisLocked=0;
uint64 FamilyTransitionPairs=0,StableFamilyPairs=0;
uint64 ThinDetachedSheets=0;
double ThinnestDetachedSheet=std::numeric_limits<double>::max();
double FamilyTransitionGradientSq=0.0,StableFamilyGradientSq=0.0;
for(float YawOffset:YawOffsets)for(float PitchOffset:PitchOffsets)
{
@@ -447,6 +489,7 @@ public:
double PreviousT=0.0;
float PreviousD=Distance(OX,OY,OZ+0.001);
bool bFound=false;
double PrimaryHitT=-1.0;
for(double T=0.5;T<=256.0;T+=0.5)
{
const double X=OX+DX*T,Y=OY+DY*T,Z=OZ+DZ*T;
@@ -460,6 +503,7 @@ public:
if(Distance(OX+DX*Mid,OY+DY*Mid,OZ+DZ*Mid)<0.0f)Lo=Mid;else Hi=Mid;
}
const double HitT=(Lo+Hi)*0.5;
PrimaryHitT=HitT;
const double XH=OX+DX*HitT,YH=OY+DY*HitT,ZH=OZ+DZ*HitT;
ESurfaceFamily Family=ESurfaceFamily::Strata;float Raw=0.0f,Detail=0.0f;
bool bFloorSurface=false;
@@ -524,6 +568,43 @@ public:
}
PreviousT=T;PreviousD=D;
}
if(PrimaryHitT>=0.0)
{
constexpr double SheetStep=0.25;
constexpr double MinimumAirGap=3.0;
constexpr double MaximumSheetThickness=1.0;
double T=PrimaryHitT+SheetStep;
float Prev=Distance(OX+DX*T,OY+DY*T,OZ+DZ*T);
bool bInSolid=Prev>=0.0f;
double SolidStart=T,LastSolidExit=PrimaryHitT;
bool bSeparatedFromPrevious=false;
for(T+=SheetStep;T<=256.0;T+=SheetStep)
{
const float D=Distance(OX+DX*T,OY+DY*T,OZ+DZ*T);
if(!bInSolid&&Prev<0.0f&&D>=0.0f)
{
SolidStart=T;
bSeparatedFromPrevious=(T-LastSolidExit)>=MinimumAirGap;
bInSolid=true;
}
else if(bInSolid&&Prev>=0.0f&&D<0.0f)
{
const double Thickness=T-SolidStart;
if(bSeparatedFromPrevious&&Thickness<MaximumSheetThickness)
{
++ThinDetachedSheets;
ThinnestDetachedSheet=std::min(ThinnestDetachedSheet,Thickness);
if(ThinDetachedSheets<=8)
std::printf("[ThinSheetCandidate] yaw_offset=%.0f pitch_offset=%.0f entry=%.2f vox thickness=%.3f vox air_gap=%.2f vox\n",
YawOffset,PitchOffset,SolidStart,Thickness,SolidStart-LastSolidExit);
}
LastSolidExit=T;
bSeparatedFromPrevious=false;
bInSolid=false;
}
Prev=D;
}
}
(void)bFound;
}
const double NearZeroShare=TotalHits?static_cast<double>(TotalNearZero)/TotalHits:1.0;
@@ -541,18 +622,23 @@ public:
const double SurfaceDetailRMS=std::sqrt(Variance);
const double RelativeJoinStep=SurfaceDetailRMS>1.0e-6?TransitionRMS/SurfaceDetailRMS:1000000.0;
const bool bDetailPass=TotalHits>=24&&std::sqrt(Variance)>=0.15&&NearZeroShare<0.50;
constexpr double GridStairThreshold=0.40;
const bool bJoinCoverage=FloorTaggedHits>=8
&&(FamilyTransitionPairs==0||(FamilyTransitionPairs>=8&&RelativeJoinStep<0.45));
&&(FamilyTransitionPairs==0||(FamilyTransitionPairs>=8&&RelativeJoinStep<GridStairThreshold));
const bool bGridPass=TotalHits>=24&&AxisShare<0.60
&&bJoinCoverage;
const bool bThinSheetPass=ThinDetachedSheets==0;
std::printf("[FlatSurfaceAudit] rays=%llu detail_stddev=%.4f vox near_zero_share=%.1f%% threshold=50.0%% status=%s\n",
static_cast<unsigned long long>(TotalHits),std::sqrt(Variance),NearZeroShare*100.0,
bDetailPass?"PASS":"FAIL");
std::printf("[GridStairAudit] rays=%llu axis_locked_normals=%.1f%% threshold=60.0%% floor_tagged_hits=%llu floor_axis_locked=%.1f%% family_transition_pairs=%llu transition_rms=%.4f vox stable_rms=%.4f vox surface_detail_rms=%.4f vox relative_join_step=%.3f threshold=0.450 legacy_join_ratio=%.3f status=%s\n",
std::printf("[GridStairAudit] rays=%llu axis_locked_normals=%.1f%% threshold=60.0%% floor_tagged_hits=%llu floor_axis_locked=%.1f%% family_transition_pairs=%llu transition_rms=%.4f vox stable_rms=%.4f vox surface_detail_rms=%.4f vox relative_join_step=%.3f threshold=%.3f legacy_join_ratio=%.3f status=%s\n",
static_cast<unsigned long long>(TotalHits),AxisShare*100.0,
static_cast<unsigned long long>(FloorTaggedHits),FloorAxisShare*100.0,
static_cast<unsigned long long>(FamilyTransitionPairs),TransitionRMS,StableRMS,
SurfaceDetailRMS,RelativeJoinStep,TransitionRatio,bGridPass?"PASS":"FAIL");
SurfaceDetailRMS,RelativeJoinStep,GridStairThreshold,TransitionRatio,bGridPass?"PASS":"FAIL");
std::printf("[ThinSheetAudit] rays=%llu isolated_sheets=%llu thickness_lt=1.000 vox air_gap_ge=3.000 vox thinnest=%.3f vox status=%s\n",
static_cast<unsigned long long>(TotalHits),static_cast<unsigned long long>(ThinDetachedSheets),
ThinDetachedSheets?ThinnestDetachedSheet:0.0,bThinSheetPass?"PASS":"FAIL");
for(size_t I=0;I<FamilyStats.size();++I)
{
const char* Name=I==0?"strata":I==1?"cave":"solid_overlay";
@@ -563,7 +649,9 @@ public:
Name,static_cast<unsigned long long>(S.Hits),M,V,
static_cast<unsigned long long>(S.NearZero),static_cast<unsigned long long>(S.AxisLocked));
}
return bDetailPass&&bGridPass;
const bool bFloorProfilePass=Stats.MaxUnmarkedFloorSlopeDelta<=0.75
&&Stats.MaxFloorCurveDeviation<=2.0;
return bDetailPass&&bGridPass&&bThinSheetPass&&bFloorProfilePass;
}
private:
@@ -606,8 +694,8 @@ private:
}
static float DetailBandFade(float SignedDistance)
{
// Keep full relief near the visible surface, then blend it to zero over
// the outer 1.5 voxels of the proxy support band.
// Keep the larger candidate mask conservative around smooth family unions;
// visible relief still fades to zero beyond its 4.125-voxel support radius.
constexpr float Edge=DetailSupremum+0.125f;
constexpr float Width=1.5f;
return SmoothUnit((Edge-std::abs(SignedDistance))/Width);
@@ -838,8 +926,7 @@ private:
const double D[3]={End[0]-Start[0],End[1]-Start[1],End[2]-Start[2]};
const double D2=D[0]*D[0]+D[1]*D[1]+D[2]*D[2];
const double T=D2>1.0e-12?(V[0]*D[0]+V[1]*D[1]+V[2]*D[2])/D2:0.0;
return static_cast<float>(P.CapsuleStartRadius
+(P.CapsuleEndRadius-P.CapsuleStartRadius)*T);
return CapsuleRadiusAtT(P,T,std::sqrt(std::max(0.0,D2)));
}
static void FitCapsuleRadiiAtEndpoints(FPrimitive& P,const FVector& PointA,float RadiusA,
@@ -867,6 +954,32 @@ private:
P.MinRadius=std::min(P.CapsuleStartRadius,P.CapsuleEndRadius);
}
static void SetCapsuleRadiusProfile(FPrimitive& P,const FVector& PointA,float RadiusA,
double SlopeA,const FVector& PointB,float RadiusB,double SlopeB)
{
const double Start[3]={P.CapsuleStartSub[0]/static_cast<double>(SubPerVoxel),
P.CapsuleStartSub[1]/static_cast<double>(SubPerVoxel),
P.CapsuleStartSub[2]/static_cast<double>(SubPerVoxel)};
const double End[3]={P.CapsuleEndSub[0]/static_cast<double>(SubPerVoxel),
P.CapsuleEndSub[1]/static_cast<double>(SubPerVoxel),
P.CapsuleEndSub[2]/static_cast<double>(SubPerVoxel)};
const double D[3]={End[0]-Start[0],End[1]-Start[1],End[2]-Start[2]};
const double D2=D[0]*D[0]+D[1]*D[1]+D[2]*D[2];
if(D2<=1.0e-12)return;
const auto Parameter=[&](const FVector& Point)
{
const double V[3]={Point.X-Start[0],Point.Y-Start[1],Point.Z-Start[2]};
return std::clamp((V[0]*D[0]+V[1]*D[1]+V[2]*D[2])/D2,0.0,1.0);
};
P.CapsuleProfileStartT=Parameter(PointA);
P.CapsuleProfileEndT=Parameter(PointB);
P.CapsuleProfileStartRadius=RadiusA;
P.CapsuleProfileEndRadius=RadiusB;
P.CapsuleRadiusSlopeStart=SlopeA;
P.CapsuleRadiusSlopeEnd=SlopeB;
P.bSmoothCapsuleRadius=P.CapsuleProfileEndT-P.CapsuleProfileStartT>1.0e-6;
}
static float SmoothedControlRadius(const TArray<float>& Radii,int32 Index,float Fallback,
float Minimum)
{
@@ -880,16 +993,163 @@ private:
+At(Index+4))*(1.0f/256.0f);
}
void AddFloorSegment(const FVector& A,const FVector& B,float Width)
static double ControlRadiusSlopeAt(const TArray<FVector>& Points,const TArray<float>& Radii,
int32 Index,float Fallback,float Minimum)
{
FVector F0=A,F1=B;F0.Z-=1.25;F1.Z-=1.25;
const auto D=Sub(F1,F0);const double Length=std::sqrt(Dot(D,D));
if(Length<1.0e-4){AddEllipsoid(F0,{Width,Width,1.25},{1,0,0},{0,1,0},{0,0,1},false);return;}
const auto U=Normalize(D);const std::array<double,3> Ref=std::abs(U[2])<0.90
?std::array<double,3>{0,0,1}:std::array<double,3>{0,1,0};
const auto V=Normalize(Cross(U,Ref)),W=Normalize(Cross(U,V));
const FVector C((F0.X+F1.X)*0.5,(F0.Y+F1.Y)*0.5,(F0.Z+F1.Z)*0.5);
AddEllipsoid(C,{Length*0.5+Width,Width,1.25},U,V,W,false);
if(Points.Num()<2||Index<0||Index>=Points.Num())return 0.0;
const auto RadiusAt=[&](int32 I)
{return static_cast<double>(SmoothedControlRadius(Radii,I,Fallback,Minimum));};
const auto LengthAt=[&](int32 I)
{
const FVector& A=Points[I];const FVector& B=Points[I+1];
const double DX=B.X-A.X,DY=B.Y-A.Y,DZ=B.Z-A.Z;
return std::sqrt(DX*DX+DY*DY+DZ*DZ);
};
if(Index==0)
{
const double Length=LengthAt(0);
return Length>1.0e-9?(RadiusAt(1)-RadiusAt(0))/Length:0.0;
}
if(Index+1==Points.Num())
{
const double Length=LengthAt(Index-1);
return Length>1.0e-9?(RadiusAt(Index)-RadiusAt(Index-1))/Length:0.0;
}
const double PrevLength=LengthAt(Index-1),NextLength=LengthAt(Index);
if(PrevLength<=1.0e-9||NextLength<=1.0e-9)return 0.0;
const double PrevSlope=(RadiusAt(Index)-RadiusAt(Index-1))/PrevLength;
const double NextSlope=(RadiusAt(Index+1)-RadiusAt(Index))/NextLength;
if(PrevSlope*NextSlope<=0.0)return 0.0;
const double W1=2.0*NextLength+PrevLength,W2=NextLength+2.0*PrevLength;
const double Harmonic=(W1+W2)/(W1/PrevSlope+W2/NextSlope);
const double Limit=3.0*std::min(std::abs(PrevSlope),std::abs(NextSlope));
return std::copysign(std::min(std::abs(Harmonic),Limit),Harmonic);
}
static double EvaluateFloorProfile(double A,double B,double TangentA,double TangentB,
double Length,double StartBlend,double EndBlend,double S)
{
const double Secant=(B-A)/Length;
if(S<0.0)return A+TangentA*S;
if(S>Length)return B+TangentB*(S-Length);
double Result=std::lerp(A,B,S/Length);
const auto Correction=[](double DeltaSlope,double Span,double T)
{
if(!(Span>0.0)||T<0.0||T>=1.0)return 0.0;
return DeltaSlope*Span*(T-2.0*T*T+T*T*T);
};
if(S<StartBlend)
Result+=Correction(TangentA-Secant,StartBlend,S/StartBlend);
if(Length-S<EndBlend)
Result-=Correction(TangentB-Secant,EndBlend,(Length-S)/EndBlend);
return Result;
}
static double FloorSurfaceRelief(double X,double Y,uint32 Seed)
{
// A shared XY field keeps independently authored floor clips from ending on
// separate flat planes. Its peak displacement stays under 1.2 voxels.
const float Broad=FStandaloneResearchTunnels::ValueNoise(
static_cast<float>(X),static_cast<float>(Y),0.0f,32,Seed^0x7F4A7C15u);
const float Mid=FStandaloneResearchTunnels::ValueNoise(
static_cast<float>(X),static_cast<float>(Y),0.0f,16,Seed^0x94D049BBu);
return 0.80*Broad+0.35*Mid;
}
static std::vector<double> BuildFloorTangents(const TArray<FVector>& Points,
const std::vector<float>& Floor,const std::vector<uint8_t>& MarkedSegments)
{
const size_t Count=Floor.size();std::vector<double> Tangent(Count,0.0);
if(Count<2||Count!=static_cast<size_t>(Points.Num())||MarkedSegments.size()+1!=Count)return Tangent;
std::vector<double> Length(Count-1,0.0),Slope(Count-1,0.0);
for(size_t I=0;I+1<Count;++I)
{
const FVector& A=Points[static_cast<int32>(I)];const FVector& B=Points[static_cast<int32>(I+1)];
Length[I]=std::hypot(B.X-A.X,B.Y-A.Y);
if(Length[I]>1.0e-6)Slope[I]=(Floor[I+1]-Floor[I])/Length[I];
}
for(size_t I=0;I<Count;++I)
{
const bool bHasPrev=I>0&&Length[I-1]>1.0e-6;
const bool bHasNext=I+1<Count&&Length[I]>1.0e-6;
if(I==0){Tangent[I]=MarkedSegments[0]?0.0:Slope[0];continue;}
if(I+1==Count){Tangent[I]=MarkedSegments[I-1]?0.0:Slope[I-1];continue;}
const bool bPrevMarked=MarkedSegments[I-1]!=0,bNextMarked=MarkedSegments[I]!=0;
if(bPrevMarked||bNextMarked)
{
if(!bPrevMarked&&bHasPrev)Tangent[I]=Slope[I-1];
else if(!bNextMarked&&bHasNext)Tangent[I]=Slope[I];
continue;
}
if(!bHasPrev||!bHasNext||Slope[I-1]*Slope[I]<=0.0)continue;
const double W0=2.0*Length[I]+Length[I-1],W1=Length[I]+2.0*Length[I-1];
const double Denominator=W0/Slope[I-1]+W1/Slope[I];
if(std::abs(Denominator)>1.0e-12)
Tangent[I]=(W0+W1)/Denominator;
}
return Tangent;
}
static void SetCurvedFloorClip(FPrimitive& P,const FVector& A,const FVector& B,
double FloorA,double FloorB,double TangentA,double TangentB)
{
if(!P.bHasFloorClip)return;
const double DX=B.X-A.X,DY=B.Y-A.Y,Length=std::hypot(DX,DY);
if(Length<=1.0e-6)return;
P.bCurvedFloorClip=true;P.FloorCurveOriginX=A.X;P.FloorCurveOriginY=A.Y;
P.FloorCurveDeltaX=DX;P.FloorCurveDeltaY=DY;P.FloorCurveLength=Length;
P.FloorCurveStartZ=FloorA;P.FloorCurveEndZ=FloorB;
P.FloorCurveTangentStart=TangentA;P.FloorCurveTangentEnd=TangentB;
P.FloorCurveStartBlend=std::min(8.0,Length*0.35);
P.FloorCurveEndBlend=std::min(8.0,Length*0.35);
for(int32 I=1;I<64;++I)
{
const double T=I/64.0;
const double Curve=EvaluateFloorProfile(FloorA,FloorB,TangentA,TangentB,
Length,P.FloorCurveStartBlend,P.FloorCurveEndBlend,Length*T);
const double Linear=std::lerp(FloorA,FloorB,T);
P.FloorCurveMaxDeviation=std::max(P.FloorCurveMaxDeviation,std::abs(Curve-Linear));
}
}
double FloorAt(const FPrimitive& P,double X,double Y) const
{
double Base=0.0;
if(P.bCurvedFloorClip)
{
const double T=((X-P.FloorCurveOriginX)*P.FloorCurveDeltaX
+(Y-P.FloorCurveOriginY)*P.FloorCurveDeltaY)
/(P.FloorCurveLength*P.FloorCurveLength);
Base=EvaluateFloorProfile(P.FloorCurveStartZ,P.FloorCurveEndZ,
P.FloorCurveTangentStart,P.FloorCurveTangentEnd,P.FloorCurveLength,
P.FloorCurveStartBlend,P.FloorCurveEndBlend,T*P.FloorCurveLength);
}
else
Base=P.FloorCenterZ+P.FloorSlopeX*(X-P.CenterSub[0]/16.0)
+P.FloorSlopeY*(Y-P.CenterSub[1]/16.0);
return Base;
}
double FloorSlopeAtEndpoint(const FPrimitive& P,bool bEnd) const
{
const double DX=P.bCurvedFloorClip?P.FloorCurveDeltaX
:P.CapsuleEndSub[0]-P.CapsuleStartSub[0];
const double DY=P.bCurvedFloorClip?P.FloorCurveDeltaY
:P.CapsuleEndSub[1]-P.CapsuleStartSub[1];
const double Length=P.bCurvedFloorClip?P.FloorCurveLength
:std::hypot(DX,DY)/SubPerVoxel;
if(Length<=1.0e-9)return 0.0;
const double OriginX=P.bCurvedFloorClip?P.FloorCurveOriginX
:P.CapsuleStartSub[0]/static_cast<double>(SubPerVoxel);
const double OriginY=P.bCurvedFloorClip?P.FloorCurveOriginY
:P.CapsuleStartSub[1]/static_cast<double>(SubPerVoxel);
const double X=OriginX+(bEnd?DX/(P.bCurvedFloorClip?1.0:SubPerVoxel):0.0);
const double Y=OriginY+(bEnd?DY/(P.bCurvedFloorClip?1.0:SubPerVoxel):0.0);
constexpr double Epsilon=0.125;
const double UX=DX/(Length*(P.bCurvedFloorClip?1.0:SubPerVoxel));
const double UY=DY/(Length*(P.bCurvedFloorClip?1.0:SubPerVoxel));
return (FloorAt(P,X+UX*Epsilon,Y+UY*Epsilon)
-FloorAt(P,X-UX*Epsilon,Y-UY*Epsilon))/(2.0*Epsilon);
}
void BuildOwnerPrimitives()
@@ -933,7 +1193,8 @@ private:
FVector(R.Center.X,R.Center.Y,R.FloorCutZ-Relief),Params,
static_cast<uint32>(Owner->Seed)).Z;
for(size_t I=RoomPrimitiveStart;I<Primitives.size();++I)
if(Primitives[I].bCarve)ApplyFloorClip(Primitives[I],FloorZ,0.0,0.0,3.0);
if(Primitives[I].bCarve)ApplyFloorClip(Primitives[I],FloorZ,0.0,0.0,
FloorClipBlendRadius);
}
// The packed signs still union room intervals exactly. Blend only
// the analytic cave field where neighboring authored rooms meet;
@@ -941,14 +1202,14 @@ private:
for(size_t I=RoomPrimitiveStart;I<Primitives.size();++I)
if(Primitives[I].bCarve)Primitives[I].BlendRadius=RoomUnionBlendRadius;
}
for(const FCachedRoomFloorJoin& J:Cache.RoomFloorJoins)
{
FVector A=J.Start,B=J.End;A.Z=J.FloorZ-1.25;B.Z=J.FloorZ-1.25;
AddFloorSegment(A,B,std::max(2.0f,J.Radius));
}
// Do not turn automatic room-overlap joins into thin positive ellipsoids.
// Their 2.5-voxel support plates can sit above the lower room floor and render as
// disconnected shards. Each room already carries its own clipped floor; the join
// metadata remains available to the owner generator and is audited here.
Stats.RoomFloorJoinCount+=static_cast<uint64>(Cache.RoomFloorJoins.Num());
const double RoomBlendRadius=ActiveBlendRadius;
ActiveBlendRadius=JoinBlendRadius;
ActiveFloorBlendRadius=3.0;
ActiveFloorBlendRadius=FloorClipBlendRadius;
for(const FCachedTunnel& T:Cache.Tunnels)
{
++Stats.TunnelCount;
@@ -958,6 +1219,34 @@ private:
const TArray<float>& Radii=bHasWorldChain?T.WorldControlRadii:T.ControlRadii;
const TArray<FTunnelFloorSegmentProfile>& FloorProfiles=bHasWorldChain
&&T.WorldFloorProfiles.Num()==Points.Num()-1?T.WorldFloorProfiles:T.FloorProfiles;
std::vector<float> RawFloor(static_cast<size_t>(Points.Num()));
std::vector<uint8_t> MarkedFloorSegments(Points.Num()>0?static_cast<size_t>(Points.Num()-1):0,0);
for(int32 I=0;I<Points.Num();++I)
RawFloor[static_cast<size_t>(I)]=static_cast<float>(Points[I].Z
-SmoothedControlRadius(Radii,I,T.RadiusA,2.0f));
if(FloorProfiles.Num()==Points.Num()-1)
{
for(int32 I=0;I+1<Points.Num();++I)
{
const FTunnelFloorSegmentProfile& Profile=FloorProfiles[I];
if(I==0)RawFloor[0]=Profile.StartFloorZ;
else RawFloor[static_cast<size_t>(I)]=0.5f*(FloorProfiles[I-1].EndFloorZ+Profile.StartFloorZ);
RawFloor[static_cast<size_t>(I+1)]=Profile.EndFloorZ;
MarkedFloorSegments[static_cast<size_t>(I)]=Profile.bLedgeTransition?1:0;
}
}
for(int32 I=0;I<Points.Num();++I)
{
const bool bPreviousUnmarked=I==0
||!MarkedFloorSegments[static_cast<size_t>(I-1)];
const bool bNextUnmarked=I+1>=Points.Num()
||!MarkedFloorSegments[static_cast<size_t>(I)];
if(bPreviousUnmarked&&bNextUnmarked)
RawFloor[static_cast<size_t>(I)]+=static_cast<float>(FloorSurfaceRelief(
Points[I].X,Points[I].Y,static_cast<uint32>(Owner->Seed)));
}
const std::vector<double> FloorTangents=BuildFloorTangents(
Points,RawFloor,MarkedFloorSegments);
for(int32 Join=1;Join+1<Points.Num();++Join)
{
const FVector A=Points[Join]-Points[Join-1],B=Points[Join+1]-Points[Join];
@@ -989,18 +1278,20 @@ private:
const FVector NextD=Points[Join+1]-Points[Join];
const double PrevXY=std::sqrt(PrevD.X*PrevD.X+PrevD.Y*PrevD.Y);
const double NextXY=std::sqrt(NextD.X*NextD.X+NextD.Y*NextD.Y);
const double PrevSlope=PrevXY>1.0e-6
const double RawPrevSlope=PrevXY>1.0e-6
?(FloorProfiles[Join-1].EndFloorZ-FloorProfiles[Join-1].StartFloorZ)/PrevXY:0.0;
const double NextSlope=NextXY>1.0e-6
const double RawNextSlope=NextXY>1.0e-6
?(FloorProfiles[Join].EndFloorZ-FloorProfiles[Join].StartFloorZ)/NextXY:0.0;
const double SlopeDelta=std::abs(PrevSlope-NextSlope);
Stats.MaxCapsuleJoinFloorSlopeDelta=std::max(Stats.MaxCapsuleJoinFloorSlopeDelta,SlopeDelta);
const double RawSlopeDelta=std::abs(RawPrevSlope-RawNextSlope);
if(FloorProfiles[Join-1].bLedgeTransition||FloorProfiles[Join].bLedgeTransition)
{
++Stats.MarkedFloorSlopeJoins;
Stats.MaxMarkedFloorSlopeDelta=std::max(Stats.MaxMarkedFloorSlopeDelta,SlopeDelta);
Stats.MaxMarkedFloorSlopeDelta=std::max(Stats.MaxMarkedFloorSlopeDelta,RawSlopeDelta);
}
else
{
Stats.MaxRawUnmarkedFloorSlopeDelta=std::max(Stats.MaxRawUnmarkedFloorSlopeDelta,RawSlopeDelta);
}
else Stats.MaxUnmarkedFloorSlopeDelta=std::max(Stats.MaxUnmarkedFloorSlopeDelta,SlopeDelta);
}
++Stats.CapsuleJoinCount;
}
@@ -1011,17 +1302,28 @@ private:
{
const float R0=SmoothedControlRadius(Radii,I,T.RadiusA,2.0f);
const float R1=SmoothedControlRadius(Radii,I+1,T.RadiusB,2.0f);
const double RadiusSlope0=ControlRadiusSlopeAt(Points,Radii,I,T.RadiusA,2.0f);
const double RadiusSlope1=ControlRadiusSlopeAt(Points,Radii,I+1,T.RadiusB,2.0f);
Stats.SmoothedProfileRadiusStep=std::max(Stats.SmoothedProfileRadiusStep,
static_cast<double>(std::abs(R1-R0)));
const FVector Segment=Points[I+1]-Points[I];
const double SegmentLength=std::sqrt(Segment.X*Segment.X+Segment.Y*Segment.Y+Segment.Z*Segment.Z);
const double Extension=ContinuousJoinExtension(R0,R1,SegmentLength);
float ZA=Points[I].Z-R0,ZB=Points[I+1].Z-R1;
if(FloorProfiles.IsValidIndex(I))ZA=FloorProfiles[I].StartFloorZ;
if(FloorProfiles.IsValidIndex(I))ZB=FloorProfiles[I].EndFloorZ;
const float ZA=RawFloor[static_cast<size_t>(I)];
const float ZB=RawFloor[static_cast<size_t>(I+1)];
AddTaperedCapsule(Points[I],Points[I+1],R0,R1,true,false,true,ZA,ZB,
I>0?Extension:0.0,I+2<Points.Num()?Extension:0.0);
if(!MarkedFloorSegments[static_cast<size_t>(I)])
{
SetCurvedFloorClip(Primitives.back(),Points[I],Points[I+1],ZA,ZB,
FloorTangents[static_cast<size_t>(I)],FloorTangents[static_cast<size_t>(I+1)]);
++Stats.CurvedFloorSegments;
Stats.MaxFloorCurveDeviation=std::max(Stats.MaxFloorCurveDeviation,
Primitives.back().FloorCurveMaxDeviation);
}
FitCapsuleRadiiAtEndpoints(Primitives.back(),Points[I],R0,Points[I+1],R1);
SetCapsuleRadiusProfile(Primitives.back(),Points[I],R0,RadiusSlope0,
Points[I+1],R1,RadiusSlope1);
}
for(int32 Join=1;Join+1<Points.Num();++Join)
{
@@ -1031,6 +1333,24 @@ private:
const float NextStart=CapsuleRadiusAt(Next,Points[Join]);
Stats.MaxCapsuleJoinRadiusDelta=std::max(Stats.MaxCapsuleJoinRadiusDelta,
static_cast<double>(std::abs(PreviousEnd-NextStart)));
Stats.MaxCapsuleJoinRadiusSlopeDelta=std::max(
Stats.MaxCapsuleJoinRadiusSlopeDelta,
std::abs(Previous.CapsuleRadiusSlopeEnd-Next.CapsuleRadiusSlopeStart));
if(!MarkedFloorSegments[static_cast<size_t>(Join-1)]
&&!MarkedFloorSegments[static_cast<size_t>(Join)])
{
const double TangentDelta=Previous.bCurvedFloorClip&&Next.bCurvedFloorClip
?std::abs(Previous.FloorCurveTangentEnd-Next.FloorCurveTangentStart)
:std::abs(FloorSlopeAtEndpoint(Previous,true)-FloorSlopeAtEndpoint(Next,false));
const double DirectionalSlopeChange=std::abs(FloorSlopeAtEndpoint(Previous,true)
-FloorSlopeAtEndpoint(Next,false));
Stats.MaxCapsuleJoinFloorSlopeDelta=std::max(
Stats.MaxCapsuleJoinFloorSlopeDelta,TangentDelta);
Stats.MaxUnmarkedFloorSlopeDelta=std::max(
Stats.MaxUnmarkedFloorSlopeDelta,TangentDelta);
Stats.MaxUnmarkedFloorDirectionSlopeDelta=std::max(
Stats.MaxUnmarkedFloorDirectionSlopeDelta,DirectionalSlopeChange);
}
}
}
else AddCapsule(T.EndpointA,T.EndpointB,std::max(T.RadiusA,T.RadiusB),true);
@@ -1052,13 +1372,25 @@ private:
}
}
const double PassageJoinBlend=ActiveBlendRadius;ActiveBlendRadius=JoinBlendRadius;
ActiveFloorBlendRadius=3.0;
ActiveFloorBlendRadius=FloorClipBlendRadius;
for(const FVoxelPassage& P:Owner->Manager->GetPassages())
{
++Stats.PassageCount;
const TArray<FVector>& Points=P.ControlPoints;
const TArray<float>& Radii=P.ControlRadii;
const size_t PassagePrimitiveStart=Primitives.size();
std::vector<float> RawFloor(static_cast<size_t>(Points.Num()));
std::vector<uint8_t> MarkedFloorSegments(Points.Num()>0?static_cast<size_t>(Points.Num()-1):0,0);
for(int32 I=0;I<Points.Num();++I)
RawFloor[static_cast<size_t>(I)]=static_cast<float>(Points[I].Z
-SmoothedControlRadius(Radii,I,P.Radius,6.0f));
if(P.NativeFloorProfileZ.Num()==Points.Num())
for(int32 I=0;I<Points.Num();++I)RawFloor[static_cast<size_t>(I)]=P.NativeFloorProfileZ[I];
for(int32 I=0;I<Points.Num();++I)
RawFloor[static_cast<size_t>(I)]+=static_cast<float>(FloorSurfaceRelief(
Points[I].X,Points[I].Y,static_cast<uint32>(Owner->Seed)));
const std::vector<double> FloorTangents=BuildFloorTangents(
Points,RawFloor,MarkedFloorSegments);
for(int32 Join=1;Join+1<Points.Num();++Join)
{
const float RPrev=std::max(6.0f,Radii.IsValidIndex(Join-1)?Radii[Join-1]:P.Radius);
@@ -1076,9 +1408,8 @@ private:
?(P.NativeFloorProfileZ[Join]-P.NativeFloorProfileZ[Join-1])/PrevXY:0.0;
const double NextSlope=NextXY>1.0e-6
?(P.NativeFloorProfileZ[Join+1]-P.NativeFloorProfileZ[Join])/NextXY:0.0;
const double SlopeDelta=std::abs(PrevSlope-NextSlope);
Stats.MaxCapsuleJoinFloorSlopeDelta=std::max(Stats.MaxCapsuleJoinFloorSlopeDelta,SlopeDelta);
Stats.MaxUnmarkedFloorSlopeDelta=std::max(Stats.MaxUnmarkedFloorSlopeDelta,SlopeDelta);
const double RawSlopeDelta=std::abs(PrevSlope-NextSlope);
Stats.MaxRawUnmarkedFloorSlopeDelta=std::max(Stats.MaxRawUnmarkedFloorSlopeDelta,RawSlopeDelta);
}
++Stats.CapsuleJoinCount;
}
@@ -1088,13 +1419,14 @@ private:
{
const float RadiusA=SmoothedControlRadius(Radii,I,P.Radius,6.0f);
const float RadiusB=SmoothedControlRadius(Radii,I+1,P.Radius,6.0f);
const double RadiusSlopeA=ControlRadiusSlopeAt(Points,Radii,I,P.Radius,6.0f);
const double RadiusSlopeB=ControlRadiusSlopeAt(Points,Radii,I+1,P.Radius,6.0f);
Stats.SmoothedProfileRadiusStep=std::max(Stats.SmoothedProfileRadiusStep,
static_cast<double>(std::abs(RadiusB-RadiusA)));
const FVector Segment=Points[I+1]-Points[I];
const double SegmentLength=std::sqrt(Segment.X*Segment.X+Segment.Y*Segment.Y+Segment.Z*Segment.Z);
float ZA=Points[I].Z-RadiusA,ZB=Points[I+1].Z-RadiusB;
if(P.NativeFloorProfileZ.IsValidIndex(I))ZA=P.NativeFloorProfileZ[I];
if(P.NativeFloorProfileZ.IsValidIndex(I+1))ZB=P.NativeFloorProfileZ[I+1];
const float ZA=RawFloor[static_cast<size_t>(I)];
const float ZB=RawFloor[static_cast<size_t>(I+1)];
const double Extension=ContinuousJoinExtension(RadiusA,RadiusB,SegmentLength);
const double CoreExtension=ContinuousJoinExtension(std::min(2.5f,RadiusA*0.45f),
std::min(2.5f,RadiusB*0.45f),SegmentLength);
@@ -1102,14 +1434,30 @@ private:
const double CoreExtendStart=I>0?CoreExtension:0.0,CoreExtendEnd=I+2<Points.Num()?CoreExtension:0.0;
AddTaperedCapsule(Points[I],Points[I+1],RadiusA,RadiusB,true,false,true,ZA,ZB,
ExtendStart,ExtendEnd);
SetCurvedFloorClip(Primitives.back(),Points[I],Points[I+1],ZA,ZB,
FloorTangents[static_cast<size_t>(I)],FloorTangents[static_cast<size_t>(I+1)]);
++Stats.CurvedFloorSegments;
Stats.MaxFloorCurveDeviation=std::max(Stats.MaxFloorCurveDeviation,
Primitives.back().FloorCurveMaxDeviation);
FitCapsuleRadiiAtEndpoints(Primitives.back(),Points[I],RadiusA,Points[I+1],RadiusB);
SetCapsuleRadiusProfile(Primitives.back(),Points[I],RadiusA,RadiusSlopeA,
Points[I+1],RadiusB,RadiusSlopeB);
const float CoreRadiusA=std::min(2.5f,RadiusA*0.45f);
const float CoreRadiusB=std::min(2.5f,RadiusB*0.45f);
const double CoreSlopeA=CoreRadiusA<2.5f-1.0e-4?RadiusSlopeA*0.45:0.0;
const double CoreSlopeB=CoreRadiusB<2.5f-1.0e-4?RadiusSlopeB*0.45:0.0;
AddTaperedCapsule(Points[I],Points[I+1],CoreRadiusA,
CoreRadiusB,true,true,true,ZA,ZB,
CoreExtendStart,CoreExtendEnd);
SetCurvedFloorClip(Primitives.back(),Points[I],Points[I+1],ZA,ZB,
FloorTangents[static_cast<size_t>(I)],FloorTangents[static_cast<size_t>(I+1)]);
++Stats.CurvedFloorSegments;
Stats.MaxFloorCurveDeviation=std::max(Stats.MaxFloorCurveDeviation,
Primitives.back().FloorCurveMaxDeviation);
FitCapsuleRadiiAtEndpoints(Primitives.back(),Points[I],CoreRadiusA,
Points[I+1],CoreRadiusB);
SetCapsuleRadiusProfile(Primitives.back(),Points[I],CoreRadiusA,CoreSlopeA,
Points[I+1],CoreRadiusB,CoreSlopeB);
}
for(int32 Join=1;Join+1<Points.Num();++Join)
{
@@ -1123,6 +1471,23 @@ private:
const float NextStart=CapsuleRadiusAt(Next,Points[Join]);
Stats.MaxCapsuleJoinRadiusDelta=std::max(Stats.MaxCapsuleJoinRadiusDelta,
static_cast<double>(std::abs(PreviousEnd-NextStart)));
Stats.MaxCapsuleJoinRadiusSlopeDelta=std::max(
Stats.MaxCapsuleJoinRadiusSlopeDelta,
std::abs(Previous.CapsuleRadiusSlopeEnd-Next.CapsuleRadiusSlopeStart));
if(Tube==0)
{
const double TangentDelta=Previous.bCurvedFloorClip&&Next.bCurvedFloorClip
?std::abs(Previous.FloorCurveTangentEnd-Next.FloorCurveTangentStart)
:std::abs(FloorSlopeAtEndpoint(Previous,true)-FloorSlopeAtEndpoint(Next,false));
const double DirectionalSlopeChange=std::abs(FloorSlopeAtEndpoint(Previous,true)
-FloorSlopeAtEndpoint(Next,false));
Stats.MaxCapsuleJoinFloorSlopeDelta=std::max(
Stats.MaxCapsuleJoinFloorSlopeDelta,TangentDelta);
Stats.MaxUnmarkedFloorSlopeDelta=std::max(
Stats.MaxUnmarkedFloorSlopeDelta,TangentDelta);
Stats.MaxUnmarkedFloorDirectionSlopeDelta=std::max(
Stats.MaxUnmarkedFloorDirectionSlopeDelta,DirectionalSlopeChange);
}
}
}
}
@@ -1382,8 +1747,7 @@ private:
if(!bInside)return false;
if(P.bHasFloorClip)
{
const double Floor=P.FloorCenterZ+P.FloorSlopeX*(X-P.CenterSub[0]/16.0)
+P.FloorSlopeY*(Y-P.CenterSub[1]/16.0);
const double Floor=FloorAt(P,X,Y);
if(P.FloorBlendRadius<=0.0)return Z>=Floor;
return SmoothMaxDistance(ShapeDistance(P,X,Y,Z),static_cast<float>(Floor-Z),
static_cast<float>(P.FloorBlendRadius))<=0.0f;
@@ -1391,6 +1755,30 @@ private:
return true;
}
static float CapsuleRadiusAtT(const FPrimitive& P,double T,double AxisLength)
{
T=std::clamp(T,0.0,1.0);
if(!P.bSmoothCapsuleRadius)
return static_cast<float>(P.CapsuleStartRadius
+(P.CapsuleEndRadius-P.CapsuleStartRadius)*T);
const double T0=P.CapsuleProfileStartT,T1=P.CapsuleProfileEndT;
if(T<T0)return static_cast<float>(P.CapsuleProfileStartRadius
+P.CapsuleRadiusSlopeStart*AxisLength*(T-T0));
if(T>T1)return static_cast<float>(P.CapsuleProfileEndRadius
+P.CapsuleRadiusSlopeEnd*AxisLength*(T-T1));
const double SpanT=std::max(1.0e-9,T1-T0);
const double U=std::clamp((T-T0)/SpanT,0.0,1.0),U2=U*U,U3=U2*U;
const double SpanDistance=AxisLength*SpanT;
const double H00=2.0*U3-3.0*U2+1.0;
const double H10=U3-2.0*U2+U;
const double H01=-2.0*U3+3.0*U2;
const double H11=U3-U2;
return static_cast<float>(H00*P.CapsuleProfileStartRadius
+H10*SpanDistance*P.CapsuleRadiusSlopeStart
+H01*P.CapsuleProfileEndRadius
+H11*SpanDistance*P.CapsuleRadiusSlopeEnd);
}
float ShapeDistance(const FPrimitive& P,double X,double Y,double Z) const
{
if(P.bCapsule)
@@ -1403,12 +1791,12 @@ private:
const double End=P.CapsuleEndSub[A]/static_cast<double>(SubPerVoxel);
V[A]=Position[A]-Start;D[A]=End-Start;D2+=D[A]*D[A];VD+=V[A]*D[A];
}
const double AxisLength=std::sqrt(std::max(0.0,D2));
const double T=D2>1.0e-12?std::clamp(VD/D2,0.0,1.0):0.0;
double DistanceSquared=0.0;
for(int32 A=0;A<3;++A)
{const double E=V[A]-T*D[A];DistanceSquared+=E*E;}
const double Radius=P.CapsuleStartRadius
+(P.CapsuleEndRadius-P.CapsuleStartRadius)*T;
const double Radius=CapsuleRadiusAtT(P,T,AxisLength);
return static_cast<float>(std::sqrt(DistanceSquared)-Radius);
}
if(P.bRoundedBox)
@@ -1455,13 +1843,13 @@ private:
H=std::min(Hi,static_cast<int32>(std::ceil(T1))+1);
}
if(L>H)return false;
if(P.bCarve&&P.bHasFloorClip)
if(P.bCarve&&P.bHasFloorClip&&(!P.bCurvedFloorClip||Axis==2))
{
double X0=Fixed[0],Y0=Fixed[1],Z0=Fixed[2];
if(Axis==0)X0=0.0;else if(Axis==1)Y0=0.0;else Z0=0.0;
const double Floor0=P.FloorCenterZ+P.FloorSlopeX*(X0-P.CenterSub[0]/16.0)
+P.FloorSlopeY*(Y0-P.CenterSub[1]/16.0);
const double Margin0=Z0-Floor0;
const double Floor0=FloorAt(P,X0,Y0);
const double SmoothAllowance=P.FloorBlendRadius>0.0?P.FloorBlendRadius/6.0:0.0;
const double Margin0=Z0-(Floor0-SmoothAllowance);
const double MarginSlope=Axis==0?-P.FloorSlopeX:Axis==1?-P.FloorSlopeY:1.0;
if(std::abs(MarginSlope)<1.0e-12)
{if(Margin0<0.0)return false;}
@@ -1814,8 +2202,7 @@ private:
float PrimitiveFloorInfluence=0.0f;
if(P.bHasFloorClip)
{
const double Floor=P.FloorCenterZ+P.FloorSlopeX*(X-P.CenterSub[0]/16.0)
+P.FloorSlopeY*(Y-P.CenterSub[1]/16.0);
const double Floor=FloorAt(P,X,Y);
const float FloorT=(static_cast<float>(std::abs(Z-Floor))-static_cast<float>(P.FloorBlendRadius))
/static_cast<float>(std::max(DetailBand*2,1));
PrimitiveFloorInfluence=1.0f-SmoothUnit(FloorT);
@@ -1866,6 +2253,7 @@ private:
const float CarveInfluence=SmoothUnit(0.5f+(Base-CarveOrOverlay)/(2.0f*FamilyBlendWidth));
const float CaveInfluence=CarveInfluence*CaveVsOverlay;
const float FloorInfluence=CaveInfluence*CaveFloorInfluence;
const float RockDistance=SmoothMinDistance(Base,CarveOrOverlay,StrataCaveBlendRadius);
if(OutFamily)
*OutFamily=Family;
if(OutSurfaceSDF)
@@ -1874,7 +2262,7 @@ private:
if(OutCaveInfluence)*OutCaveInfluence=CaveInfluence;
if(OutFloorInfluence)*OutFloorInfluence=FloorInfluence;
if(OutFloorSurface)*OutFloorSurface=FloorInfluence>=0.5f;
return SmoothMinDistance(Base,CarveOrOverlay,StrataCaveBlendRadius);
return RockDistance;
}
float OwnerCaveRoughness(double X,double Y,double Z,float SurfaceSDF,
@@ -1937,9 +2325,13 @@ private:
static float SurfaceRelief(float X,float Y,float Z,uint32 Seed)
{
return 0.70f*FStandaloneResearchTunnels::ValueNoise(X,Y,Z,3,Seed^0xA511E9B3u)
+1.00f*FStandaloneResearchTunnels::ValueNoise(X,Y,Z,6,Seed^0x63D83595u)
+1.00f*FStandaloneResearchTunnels::ValueNoise(X,Y,Z,12,Seed^0xB5297A4Du);
// The low band carries the broad rock shapes and also gently varies mid-band
// amplitude. The quieter 6-voxel octave keeps fine relief without restoring
// the same tight bubble pattern on every exposed surface.
const float Macro=FStandaloneResearchTunnels::ValueNoise(X,Y,Z,32,Seed^0xB5297A4Du);
const float Mid=FStandaloneResearchTunnels::ValueNoise(X,Y,Z,16,Seed^0x63D83595u);
const float Fine=FStandaloneResearchTunnels::ValueNoise(X,Y,Z,6,Seed^0xA511E9B3u);
return 1.00f*Macro+(0.80f+0.25f*Macro)*(0.85f*Mid+0.18f*Fine);
}
float DetailNoise(double X,double Y,double Z,float SurfaceSDF,