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:
1 parent
7e3f926352
commit
97c0455939
12 files changed
+919
-108
No files matched your search
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user