fix(tunnels): ribs removed at the cause; authored tunnel floor removed; slope acts on the route
The owner proved in game that the tunnel core caused the ribs (voxel.TunnelAblateTunnelCore=1 removes them). Root cause: the core's final reassertion replaced any sufficiently interior sample with a fixed +16 air density, destroying the distance gradient, so marching cubes snapped the surface to the voxel grid (all 385 centre samples saturated). The core now reasserts the continuous -Core.SDF, with bottom polarity from the swept shape. The Catmull-Rom sweep from0231072is reverted (it cost 18-30% and did not help); the linear tapered-capsule sweep is restored. Owner decisions implemented: bTunnelFloorEnabled has no runtime effect (reflected only for asset compatibility, ignored on PostLoad), and the authored slab / graph support-floor paths are detached from generation, so the tunnel's own carved shape is the floor. Winding and dramatic ledges no longer depend on the floor switch. Degrees authoring, its migration, and the 3840-voxel route cap stay. Mouth trim stays behind voxel.TunnelMouthTrim (default off). Evidence: along owner tunnel 15, 385 samples at 0.25 voxel, periodic surface amplitude floor 3.55 -> 0.50 cm, wall 16.9 -> 1.10 cm, ceiling 8.80 -> 0.69 cm (acceptance < 2 cm); 0/385 samples saturated. Matched-camera render: ribs absent. Canonical walk: Connected, player-fit 41,184 (was 20,830), walk-only 13,879 (was 10,909), mouth gap 0; mesh identical with -voxel.CrtFma3=0. TilePostReachProof, DetSinCos, strate connectivity 16/16, strate crossing: pass. Perf, one S/M/S/M pair vs a rebuiltbf9384aDLL: static 26.10 vs 25.41 s (+2.7%), moving 55.34 vs 53.08 s (+4.2%), request p95 about equal. Treat as a possible small cost to re-measure, not as noise. Not merged into experimental: waiting for the owner's in-game look. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
0231072cc4
commit
4fa3931176
11 files changed
+1237
-855
No files matched your search
@@ -82,6 +82,9 @@ namespace
|
|||||||
{
|
{
|
||||||
float SDF = FLT_MAX;
|
float SDF = FLT_MAX;
|
||||||
bool bSupportFloor = false;
|
bool bSupportFloor = false;
|
||||||
|
bool bHasSweptFloor = false;
|
||||||
|
float SweptFloorZ = -FLT_MAX;
|
||||||
|
float SweptFloorRadius = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
// FMath::PerlinNoise2D is the same two-octave field used by room floors. The derivative bound
|
// FMath::PerlinNoise2D is the same two-octave field used by room floors. The derivative bound
|
||||||
@@ -621,6 +624,11 @@ namespace
|
|||||||
struct FVFRoomFloorOwnership
|
struct FVFRoomFloorOwnership
|
||||||
{
|
{
|
||||||
bool bValid = false;
|
bool bValid = false;
|
||||||
|
// The room-floor blend is intentionally short, but the tunnel capsule and the fitted
|
||||||
|
// player envelope can still overlap the room beyond that blend. This separate hand-off
|
||||||
|
// bit prevents the graph tunnel's lower-side polarity from turning that overlap into a
|
||||||
|
// wall without extending the room floor itself into the corridor.
|
||||||
|
bool bSuppressTunnelBottom = false;
|
||||||
float FloorZ = 0.0f;
|
float FloorZ = 0.0f;
|
||||||
float Weight = 0.0f;
|
float Weight = 0.0f;
|
||||||
uint32 RoomHash = 0;
|
uint32 RoomHash = 0;
|
||||||
@@ -670,7 +678,13 @@ namespace
|
|||||||
: (RelativeDistance < Fade
|
: (RelativeDistance < Fade
|
||||||
? 1.0f - SmoothStep01(RelativeDistance / Fade)
|
? 1.0f - SmoothStep01(RelativeDistance / Fade)
|
||||||
: 0.0f);
|
: 0.0f);
|
||||||
if (!(Weight > 0.0f))
|
const float EndpointRadius = FMath::Abs(ControlRadiusData[ControlIndex]);
|
||||||
|
const float BottomHandoffRadius = BlendRadius
|
||||||
|
+ VF_TunnelMouthPlayerFitOverlapVoxels
|
||||||
|
+ EndpointRadius;
|
||||||
|
const bool bInBottomHandoff = VoxelMath::IsFinite(BottomHandoffRadius)
|
||||||
|
&& Distance <= BottomHandoffRadius;
|
||||||
|
if (!(Weight > 0.0f) && !bInBottomHandoff)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -694,7 +708,8 @@ namespace
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
BestScore = Score;
|
BestScore = Score;
|
||||||
Result.bValid = true;
|
Result.bValid = Weight > 0.0f;
|
||||||
|
Result.bSuppressTunnelBottom = bInBottomHandoff;
|
||||||
Result.FloorZ = FloorZ;
|
Result.FloorZ = FloorZ;
|
||||||
Result.Weight = Weight;
|
Result.Weight = Weight;
|
||||||
Result.RoomHash = Endpoint == 0 ? FloorRoomHashA : FloorRoomHashB;
|
Result.RoomHash = Endpoint == 0 ? FloorRoomHashA : FloorRoomHashB;
|
||||||
@@ -897,146 +912,6 @@ namespace
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A world-chain handoff is sampled after the cave-warped SDF chain has already carved the
|
|
||||||
// tunnel. It must follow the same continuous centreline, not union another tapered capsule
|
|
||||||
// at every control point. A fixed Catmull-Rom span gives adjacent spans a shared tangent;
|
|
||||||
// the fixed projection iterations keep the result deterministic and bounded.
|
|
||||||
struct FVFTunnelCubicSpan
|
|
||||||
{
|
|
||||||
FVector P0 = FVector::ZeroVector;
|
|
||||||
FVector P1 = FVector::ZeroVector;
|
|
||||||
FVector P2 = FVector::ZeroVector;
|
|
||||||
FVector P3 = FVector::ZeroVector;
|
|
||||||
float R0 = 0.0f;
|
|
||||||
float R1 = 0.0f;
|
|
||||||
float R2 = 0.0f;
|
|
||||||
float R3 = 0.0f;
|
|
||||||
|
|
||||||
FORCEINLINE FVector Position(float T) const
|
|
||||||
{
|
|
||||||
const float T2 = T * T;
|
|
||||||
const float T3 = T2 * T;
|
|
||||||
return (P1 * 2.0f
|
|
||||||
+ (P2 - P0) * T
|
|
||||||
+ (P0 * 2.0f - P1 * 5.0f + P2 * 4.0f - P3) * T2
|
|
||||||
+ (-P0 + P1 * 3.0f - P2 * 3.0f + P3) * T3) * 0.5f;
|
|
||||||
}
|
|
||||||
|
|
||||||
FORCEINLINE FVector FirstDerivative(float T) const
|
|
||||||
{
|
|
||||||
const float T2 = T * T;
|
|
||||||
return ((P2 - P0)
|
|
||||||
+ (P0 * 2.0f - P1 * 5.0f + P2 * 4.0f - P3) * (2.0f * T)
|
|
||||||
+ (-P0 + P1 * 3.0f - P2 * 3.0f + P3) * (3.0f * T2)) * 0.5f;
|
|
||||||
}
|
|
||||||
|
|
||||||
FORCEINLINE FVector SecondDerivative(float T) const
|
|
||||||
{
|
|
||||||
return ((P0 * 2.0f - P1 * 5.0f + P2 * 4.0f - P3) * 2.0f
|
|
||||||
+ (-P0 + P1 * 3.0f - P2 * 3.0f + P3) * (6.0f * T)) * 0.5f;
|
|
||||||
}
|
|
||||||
|
|
||||||
FORCEINLINE float Radius(float T) const
|
|
||||||
{
|
|
||||||
const float T2 = T * T;
|
|
||||||
const float T3 = T2 * T;
|
|
||||||
const float Value = (R1 * 2.0f
|
|
||||||
+ (R2 - R0) * T
|
|
||||||
+ (R0 * 2.0f - R1 * 5.0f + R2 * 4.0f - R3) * T2
|
|
||||||
+ (-R0 + R1 * 3.0f - R2 * 3.0f + R3) * T3) * 0.5f;
|
|
||||||
return FMath::Max(Value, 0.5f);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static FVFTunnelCubicSpan VF_MakeTunnelCubicSpan(
|
|
||||||
const TArray<FVector>& ControlPoints,
|
|
||||||
const TArray<float>& ControlRadii,
|
|
||||||
int32 SegmentIndex)
|
|
||||||
{
|
|
||||||
const int32 LastPoint = ControlPoints.Num() - 1;
|
|
||||||
const FVector& P1 = ControlPoints[SegmentIndex];
|
|
||||||
const FVector& P2 = ControlPoints[SegmentIndex + 1];
|
|
||||||
const FVector P0 = SegmentIndex > 0
|
|
||||||
? ControlPoints[SegmentIndex - 1] : P1 + (P1 - P2);
|
|
||||||
const FVector P3 = SegmentIndex + 2 <= LastPoint
|
|
||||||
? ControlPoints[SegmentIndex + 2] : P2 + (P2 - P1);
|
|
||||||
const float R1 = ControlRadii[SegmentIndex];
|
|
||||||
const float R2 = ControlRadii[SegmentIndex + 1];
|
|
||||||
const float R0 = SegmentIndex > 0
|
|
||||||
? ControlRadii[SegmentIndex - 1] : R1 + (R1 - R2);
|
|
||||||
const float R3 = SegmentIndex + 2 <= LastPoint
|
|
||||||
? ControlRadii[SegmentIndex + 2] : R2 + (R2 - R1);
|
|
||||||
|
|
||||||
FVFTunnelCubicSpan Span;
|
|
||||||
Span.P0 = P0;
|
|
||||||
Span.P1 = P1;
|
|
||||||
Span.P2 = P2;
|
|
||||||
Span.P3 = P3;
|
|
||||||
Span.R0 = R0;
|
|
||||||
Span.R1 = R1;
|
|
||||||
Span.R2 = R2;
|
|
||||||
Span.R3 = R3;
|
|
||||||
return Span;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool VF_ProjectTunnelCubicXY(
|
|
||||||
const FVector& Position,
|
|
||||||
const FVFTunnelCubicSpan& Span,
|
|
||||||
float& OutT,
|
|
||||||
float& OutDistanceSquared)
|
|
||||||
{
|
|
||||||
float BestT = 0.0f;
|
|
||||||
float BestDistanceSquared = FLT_MAX;
|
|
||||||
auto Consider = [&](float T)
|
|
||||||
{
|
|
||||||
const FVector Candidate = Span.Position(T);
|
|
||||||
const float DX = static_cast<float>(Candidate.X - Position.X);
|
|
||||||
const float DY = static_cast<float>(Candidate.Y - Position.Y);
|
|
||||||
const float DistanceSquared = DX * DX + DY * DY;
|
|
||||||
if (DistanceSquared < BestDistanceSquared)
|
|
||||||
{
|
|
||||||
BestDistanceSquared = DistanceSquared;
|
|
||||||
BestT = T;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr int32 InitialSamples = 8;
|
|
||||||
for (int32 Sample = 0; Sample <= InitialSamples; ++Sample)
|
|
||||||
{
|
|
||||||
Consider(static_cast<float>(Sample) / InitialSamples);
|
|
||||||
}
|
|
||||||
float CandidateT = BestT;
|
|
||||||
for (int32 Iteration = 0; Iteration < 6; ++Iteration)
|
|
||||||
{
|
|
||||||
const FVector CurvePoint = Span.Position(CandidateT);
|
|
||||||
const FVector First = Span.FirstDerivative(CandidateT);
|
|
||||||
const FVector Second = Span.SecondDerivative(CandidateT);
|
|
||||||
const float DX = static_cast<float>(CurvePoint.X - Position.X);
|
|
||||||
const float DY = static_cast<float>(CurvePoint.Y - Position.Y);
|
|
||||||
const float FirstX = static_cast<float>(First.X);
|
|
||||||
const float FirstY = static_cast<float>(First.Y);
|
|
||||||
const float SecondX = static_cast<float>(Second.X);
|
|
||||||
const float SecondY = static_cast<float>(Second.Y);
|
|
||||||
const float Numerator = DX * FirstX + DY * FirstY;
|
|
||||||
const float Denominator = FirstX * FirstX + FirstY * FirstY
|
|
||||||
+ DX * SecondX + DY * SecondY;
|
|
||||||
if (!VoxelMath::IsFinite(Numerator)
|
|
||||||
|| !VoxelMath::IsFinite(Denominator)
|
|
||||||
|| FMath::Abs(Denominator) <= KINDA_SMALL_NUMBER)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
CandidateT = FMath::Clamp(
|
|
||||||
CandidateT - Numerator / Denominator, 0.0f, 1.0f);
|
|
||||||
Consider(CandidateT);
|
|
||||||
}
|
|
||||||
|
|
||||||
OutT = BestT;
|
|
||||||
OutDistanceSquared = BestDistanceSquared;
|
|
||||||
return VoxelMath::IsFinite(OutT)
|
|
||||||
&& VoxelMath::IsFinite(OutDistanceSquared);
|
|
||||||
}
|
|
||||||
|
|
||||||
static FVFTunnelShapeEvaluation VF_EvaluateSweptTunnelChain(
|
static FVFTunnelShapeEvaluation VF_EvaluateSweptTunnelChain(
|
||||||
const FVector& Position,
|
const FVector& Position,
|
||||||
const TArray<FVector>& ControlPoints,
|
const TArray<FVector>& ControlPoints,
|
||||||
@@ -1044,7 +919,6 @@ namespace
|
|||||||
const FCachedTunnel& Tunnel,
|
const FCachedTunnel& Tunnel,
|
||||||
float SDFBlendRadius,
|
float SDFBlendRadius,
|
||||||
bool bApplyFloorCut,
|
bool bApplyFloorCut,
|
||||||
bool bUseContinuousCurve,
|
|
||||||
const void* CacheIdentity, int32 TunnelIndex,
|
const void* CacheIdentity, int32 TunnelIndex,
|
||||||
const FVFFloorReliefColumnKey& ReliefColumn,
|
const FVFFloorReliefColumnKey& ReliefColumn,
|
||||||
const TArray<FTunnelFloorSegmentProfile>* FloorProfiles,
|
const TArray<FTunnelFloorSegmentProfile>* FloorProfiles,
|
||||||
@@ -1077,13 +951,7 @@ namespace
|
|||||||
const float RadiusB = ControlRadiusData[SegmentIndex + 1];
|
const float RadiusB = ControlRadiusData[SegmentIndex + 1];
|
||||||
float T = 0.0f;
|
float T = 0.0f;
|
||||||
float HorizontalDistanceSquared = FLT_MAX;
|
float HorizontalDistanceSquared = FLT_MAX;
|
||||||
const FVFTunnelCubicSpan Span = bUseContinuousCurve
|
const bool bHasHorizontalProjection = VF_ProjectTunnelSegmentXY(
|
||||||
? VF_MakeTunnelCubicSpan(ControlPoints, ControlRadii, SegmentIndex)
|
|
||||||
: FVFTunnelCubicSpan();
|
|
||||||
const bool bHasHorizontalProjection = bUseContinuousCurve
|
|
||||||
? VF_ProjectTunnelCubicXY(
|
|
||||||
Position, Span, T, HorizontalDistanceSquared)
|
|
||||||
: VF_ProjectTunnelSegmentXY(
|
|
||||||
Position, A, B, T, HorizontalDistanceSquared);
|
Position, A, B, T, HorizontalDistanceSquared);
|
||||||
if (!bHasHorizontalProjection)
|
if (!bHasHorizontalProjection)
|
||||||
{
|
{
|
||||||
@@ -1095,10 +963,6 @@ namespace
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FVector CurvePoint = bUseContinuousCurve
|
|
||||||
? Span.Position(T) : FMath::Lerp(A, B, T);
|
|
||||||
const float CurveRadius = bUseContinuousCurve
|
|
||||||
? Span.Radius(T) : FMath::Lerp(RadiusA, RadiusB, T);
|
|
||||||
const FTunnelFloorSegmentProfile* Profile = FloorProfileData != nullptr
|
const FTunnelFloorSegmentProfile* Profile = FloorProfileData != nullptr
|
||||||
? FloorProfileData + SegmentIndex : nullptr;
|
? FloorProfileData + SegmentIndex : nullptr;
|
||||||
const FVFTunnelFloorTerms FloorTerms = VF_TunnelFloorTerms(
|
const FVFTunnelFloorTerms FloorTerms = VF_TunnelFloorTerms(
|
||||||
@@ -1137,13 +1001,12 @@ namespace
|
|||||||
// headroom in every gentle tunnel merely because a profile is now always present.
|
// headroom in every gentle tunnel merely because a profile is now always present.
|
||||||
const bool bUseAuthoredProfile = bApplyFloorCut
|
const bool bUseAuthoredProfile = bApplyFloorCut
|
||||||
&& Profile != nullptr
|
&& Profile != nullptr
|
||||||
&& Tunnel.bTunnelFloorEnabled
|
|
||||||
&& (Tunnel.bFloorRouteWasWound || Tunnel.bDramaticLedge);
|
&& (Tunnel.bFloorRouteWasWound || Tunnel.bDramaticLedge);
|
||||||
const bool bAnchorCapsule = bUseAuthoredProfile
|
const bool bAnchorCapsule = bUseAuthoredProfile
|
||||||
&& VoxelMath::IsFinite(NaturalFloorZ);
|
&& VoxelMath::IsFinite(NaturalFloorZ);
|
||||||
const auto EvaluateAnchoredShape = [
|
const auto EvaluateAnchoredShape = [
|
||||||
&Position, &A, &B, RadiusA, RadiusB, CurvePoint, CurveRadius,
|
&Position, &A, &B, RadiusA, RadiusB,
|
||||||
NaturalFloorZ, bAnchorCapsule, bUseContinuousCurve]
|
NaturalFloorZ, bAnchorCapsule]
|
||||||
(float FloorZ) -> float
|
(float FloorZ) -> float
|
||||||
{
|
{
|
||||||
const float Shift = FloorZ - NaturalFloorZ;
|
const float Shift = FloorZ - NaturalFloorZ;
|
||||||
@@ -1151,15 +1014,11 @@ namespace
|
|||||||
|| !VoxelMath::IsFinite(Shift)
|
|| !VoxelMath::IsFinite(Shift)
|
||||||
|| FMath::Abs(Shift) <= KINDA_SMALL_NUMBER)
|
|| FMath::Abs(Shift) <= KINDA_SMALL_NUMBER)
|
||||||
{
|
{
|
||||||
return bUseContinuousCurve
|
return VoxelSDF::TaperedCapsule(
|
||||||
? FVector::Dist(Position, CurvePoint) - CurveRadius
|
|
||||||
: VoxelSDF::TaperedCapsule(
|
|
||||||
Position, A, B, RadiusA, RadiusB);
|
Position, A, B, RadiusA, RadiusB);
|
||||||
}
|
}
|
||||||
const FVector Offset(0.0f, 0.0f, Shift);
|
const FVector Offset(0.0f, 0.0f, Shift);
|
||||||
return bUseContinuousCurve
|
return VoxelSDF::TaperedCapsule(
|
||||||
? FVector::Dist(Position, CurvePoint + Offset) - CurveRadius
|
|
||||||
: VoxelSDF::TaperedCapsule(
|
|
||||||
Position, A + Offset, B + Offset, RadiusA, RadiusB);
|
Position, A + Offset, B + Offset, RadiusA, RadiusB);
|
||||||
};
|
};
|
||||||
float SegmentSDF = EvaluateAnchoredShape(FloorTerms.BaseFloorZ);
|
float SegmentSDF = EvaluateAnchoredShape(FloorTerms.BaseFloorZ);
|
||||||
@@ -1211,18 +1070,26 @@ namespace
|
|||||||
? VoxelSDF::SmoothMax(
|
? VoxelSDF::SmoothMax(
|
||||||
SegmentSDF, FloorZ - static_cast<float>(Position.Z), FloorBlend)
|
SegmentSDF, FloorZ - static_cast<float>(Position.Z), FloorBlend)
|
||||||
: SegmentSDF;
|
: SegmentSDF;
|
||||||
Result.SDF = FMath::Min(Result.SDF, SegmentResultSDF);
|
if (SegmentResultSDF < Result.SDF)
|
||||||
|
|
||||||
if (bApplyFloorCut && Tunnel.bTunnelFloorEnabled
|
|
||||||
&& SegmentSDF <= 0.0f
|
|
||||||
&& HorizontalDistanceSquared <= FMath::Square(SupportRadius)
|
|
||||||
&& Position.Z >= FloorZ
|
|
||||||
- VoxelPassageGeometry::LandingFloorThicknessVoxels
|
|
||||||
&& Position.Z <= FloorZ
|
|
||||||
+ VoxelPassageGeometry::WalkableTunnelFloorAirClearanceVoxels)
|
|
||||||
{
|
{
|
||||||
Result.bSupportFloor = true;
|
Result.SDF = SegmentResultSDF;
|
||||||
|
// This is shape metadata, not a second floor field. It records which side of
|
||||||
|
// the winning swept SDF is its own bottom so the final MC hand-off can restore
|
||||||
|
// that side when a room's generic air field would otherwise erase it.
|
||||||
|
Result.bHasSweptFloor = bApplyFloorCut
|
||||||
|
&& VoxelMath::IsFinite(FloorZ)
|
||||||
|
&& VoxelMath::IsFinite(SupportRadius)
|
||||||
|
&& VoxelMath::IsFinite(HorizontalDistanceSquared)
|
||||||
|
&& HorizontalDistanceSquared <= FMath::Square(SupportRadius)
|
||||||
|
// The room owns the finite mouth apron. Do not let the tunnel's bottom
|
||||||
|
// polarity turn that overlap into a wall; the corridor interior remains
|
||||||
|
// owned by this swept shape.
|
||||||
|
&& !(RoomFloorOwnership != nullptr
|
||||||
|
&& RoomFloorOwnership->bSuppressTunnelBottom);
|
||||||
|
Result.SweptFloorZ = Result.bHasSweptFloor ? FloorZ : -FLT_MAX;
|
||||||
|
Result.SweptFloorRadius = Result.bHasSweptFloor ? SupportRadius : 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
@@ -1249,7 +1116,6 @@ namespace
|
|||||||
return VF_EvaluateSweptTunnelChain(
|
return VF_EvaluateSweptTunnelChain(
|
||||||
Position, ShapeControlPoints, ShapeControlRadii,
|
Position, ShapeControlPoints, ShapeControlRadii,
|
||||||
Tunnel, SDFBlendRadius, bApplyFloorCut,
|
Tunnel, SDFBlendRadius, bApplyFloorCut,
|
||||||
bWorldChain,
|
|
||||||
CacheIdentity, TunnelIndex, ReliefColumn,
|
CacheIdentity, TunnelIndex, ReliefColumn,
|
||||||
bWorldChain ? &Tunnel.WorldFloorProfiles : &Tunnel.FloorProfiles,
|
bWorldChain ? &Tunnel.WorldFloorProfiles : &Tunnel.FloorProfiles,
|
||||||
RoomFloorOwnership);
|
RoomFloorOwnership);
|
||||||
@@ -1270,7 +1136,6 @@ namespace
|
|||||||
return VF_EvaluateSweptTunnelChain(
|
return VF_EvaluateSweptTunnelChain(
|
||||||
Position, Points, Radii,
|
Position, Points, Radii,
|
||||||
Tunnel, SDFBlendRadius, bApplyFloorCut,
|
Tunnel, SDFBlendRadius, bApplyFloorCut,
|
||||||
bWorldChain,
|
|
||||||
CacheIdentity, TunnelIndex, ReliefColumn,
|
CacheIdentity, TunnelIndex, ReliefColumn,
|
||||||
bWorldChain ? &Tunnel.WorldFloorProfiles : &Tunnel.FloorProfiles,
|
bWorldChain ? &Tunnel.WorldFloorProfiles : &Tunnel.FloorProfiles,
|
||||||
RoomFloorOwnership);
|
RoomFloorOwnership);
|
||||||
@@ -1285,7 +1150,6 @@ namespace
|
|||||||
return VF_EvaluateSweptTunnelChain(
|
return VF_EvaluateSweptTunnelChain(
|
||||||
Position, Points, Radii,
|
Position, Points, Radii,
|
||||||
Tunnel, SDFBlendRadius, bApplyFloorCut,
|
Tunnel, SDFBlendRadius, bApplyFloorCut,
|
||||||
bWorldChain,
|
|
||||||
CacheIdentity, TunnelIndex, ReliefColumn,
|
CacheIdentity, TunnelIndex, ReliefColumn,
|
||||||
bWorldChain ? &Tunnel.WorldFloorProfiles : &Tunnel.FloorProfiles,
|
bWorldChain ? &Tunnel.WorldFloorProfiles : &Tunnel.FloorProfiles,
|
||||||
RoomFloorOwnership);
|
RoomFloorOwnership);
|
||||||
@@ -1300,10 +1164,6 @@ namespace
|
|||||||
const void* CacheIdentity, int32 TunnelIndex,
|
const void* CacheIdentity, int32 TunnelIndex,
|
||||||
const FVFRoomFloorOwnership* RoomFloorOwnership = nullptr)
|
const FVFRoomFloorOwnership* RoomFloorOwnership = nullptr)
|
||||||
{
|
{
|
||||||
if (!Tunnel.bTunnelFloorEnabled)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const FVFFloorReliefColumnKey ReliefColumn =
|
const FVFFloorReliefColumnKey ReliefColumn =
|
||||||
VF_MakeFloorReliefColumnKey(
|
VF_MakeFloorReliefColumnKey(
|
||||||
static_cast<float>(Position.X), static_cast<float>(Position.Y));
|
static_cast<float>(Position.X), static_cast<float>(Position.Y));
|
||||||
@@ -1780,12 +1640,6 @@ void FChunkSDFCache::Reset()
|
|||||||
PitSpatialIndex.Reset();
|
PitSpatialIndex.Reset();
|
||||||
ChimneySpatialIndex.Reset();
|
ChimneySpatialIndex.Reset();
|
||||||
ColumnSpatialIndex.Reset();
|
ColumnSpatialIndex.Reset();
|
||||||
SupportColumnMinX = 0;
|
|
||||||
SupportColumnMinY = 0;
|
|
||||||
SupportColumnCellsX = 0;
|
|
||||||
SupportColumnCellsY = 0;
|
|
||||||
SupportColumnEntries.Reset();
|
|
||||||
SupportColumns.Reset();
|
|
||||||
SDFBlendRadius = 0.0f;
|
SDFBlendRadius = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1803,12 +1657,6 @@ void FChunkSDFCache::Release()
|
|||||||
PitSpatialIndex.Release();
|
PitSpatialIndex.Release();
|
||||||
ChimneySpatialIndex.Release();
|
ChimneySpatialIndex.Release();
|
||||||
ColumnSpatialIndex.Release();
|
ColumnSpatialIndex.Release();
|
||||||
SupportColumnEntries.Empty();
|
|
||||||
SupportColumns.Empty();
|
|
||||||
SupportColumnMinX = 0;
|
|
||||||
SupportColumnMinY = 0;
|
|
||||||
SupportColumnCellsX = 0;
|
|
||||||
SupportColumnCellsY = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SIZE_T FChunkSDFCache::GetAllocatedSize() const
|
SIZE_T FChunkSDFCache::GetAllocatedSize() const
|
||||||
@@ -1830,8 +1678,6 @@ VoxelDensityProfile::FCacheMemoryBreakdown FChunkSDFCache::GetAllocatedSizeBreak
|
|||||||
Breakdown.PitsBytes = ArrayBytes(Pits);
|
Breakdown.PitsBytes = ArrayBytes(Pits);
|
||||||
Breakdown.ChimneysBytes = ArrayBytes(Chimneys);
|
Breakdown.ChimneysBytes = ArrayBytes(Chimneys);
|
||||||
Breakdown.ColumnsBytes = ArrayBytes(Columns);
|
Breakdown.ColumnsBytes = ArrayBytes(Columns);
|
||||||
Breakdown.SupportColumnEntriesBytes = ArrayBytes(SupportColumnEntries);
|
|
||||||
Breakdown.SupportColumnsBytes = ArrayBytes(SupportColumns);
|
|
||||||
Breakdown.SpatialIndexBytes = static_cast<uint64>(
|
Breakdown.SpatialIndexBytes = static_cast<uint64>(
|
||||||
RoomSpatialIndex.GetAllocatedSize()
|
RoomSpatialIndex.GetAllocatedSize()
|
||||||
+ RoomFloorJoinSpatialIndex.GetAllocatedSize()
|
+ RoomFloorJoinSpatialIndex.GetAllocatedSize()
|
||||||
@@ -1839,11 +1685,6 @@ VoxelDensityProfile::FCacheMemoryBreakdown FChunkSDFCache::GetAllocatedSizeBreak
|
|||||||
+ PitSpatialIndex.GetAllocatedSize()
|
+ PitSpatialIndex.GetAllocatedSize()
|
||||||
+ ChimneySpatialIndex.GetAllocatedSize()
|
+ ChimneySpatialIndex.GetAllocatedSize()
|
||||||
+ ColumnSpatialIndex.GetAllocatedSize());
|
+ ColumnSpatialIndex.GetAllocatedSize());
|
||||||
for (const FTunnelSupportFloorColumn& Column : SupportColumns)
|
|
||||||
{
|
|
||||||
Breakdown.SupportColumnIntervalsBytes += ArrayBytes(Column.Intervals);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const FCachedTunnel& Tunnel : Tunnels)
|
for (const FCachedTunnel& Tunnel : Tunnels)
|
||||||
{
|
{
|
||||||
Breakdown.TunnelsBytes += ArrayBytes(Tunnel.ControlPoints);
|
Breakdown.TunnelsBytes += ArrayBytes(Tunnel.ControlPoints);
|
||||||
@@ -2387,12 +2228,10 @@ static bool VF_ComputeRoomGraphReach(
|
|||||||
const float GentleSlopeThreshold = VoxelPassageGeometry::TunnelFloorGradientFromDegrees(
|
const float GentleSlopeThreshold = VoxelPassageGeometry::TunnelFloorGradientFromDegrees(
|
||||||
Params.TunnelFloorGentleSlopeThreshold);
|
Params.TunnelFloorGentleSlopeThreshold);
|
||||||
const float MaxFloorDeltaEnvelope = 2.0f * EndpointVertical;
|
const float MaxFloorDeltaEnvelope = 2.0f * EndpointVertical;
|
||||||
const float RequiredRouteRunEnvelope = Params.bTunnelFloorEnabled
|
const float RequiredRouteRunEnvelope = FMath::Min(
|
||||||
? FMath::Min(
|
|
||||||
VoxelPassageGeometry::TunnelFloorMaximumRouteLengthVoxels,
|
VoxelPassageGeometry::TunnelFloorMaximumRouteLengthVoxels,
|
||||||
MaxFloorDeltaEnvelope / FMath::Max(
|
MaxFloorDeltaEnvelope / FMath::Max(
|
||||||
GentleSlopeThreshold, KINDA_SMALL_NUMBER))
|
GentleSlopeThreshold, KINDA_SMALL_NUMBER));
|
||||||
: 0.0f;
|
|
||||||
const float OrganicLateralEnvelope = FMath::Min(
|
const float OrganicLateralEnvelope = FMath::Min(
|
||||||
FMath::Abs(Params.TunnelWarpStrength), TunnelLengthBound * 0.25f);
|
FMath::Abs(Params.TunnelWarpStrength), TunnelLengthBound * 0.25f);
|
||||||
const float RouteLateralEnvelope = FMath::Max(
|
const float RouteLateralEnvelope = FMath::Max(
|
||||||
@@ -7023,7 +6862,6 @@ void VoxelCaveMorphology::BuildChunkCache(
|
|||||||
CT.bHasFloorRoomOwnership = true;
|
CT.bHasFloorRoomOwnership = true;
|
||||||
CT.FloorRoomHashA = RoomA.Hash;
|
CT.FloorRoomHashA = RoomA.Hash;
|
||||||
CT.FloorRoomHashB = RoomB.Hash;
|
CT.FloorRoomHashB = RoomB.Hash;
|
||||||
CT.bTunnelFloorEnabled = Params.bTunnelFloorEnabled;
|
|
||||||
CT.bTunnelFloorTerracingEnabled = Params.bTunnelFloorTerracingEnabled;
|
CT.bTunnelFloorTerracingEnabled = Params.bTunnelFloorTerracingEnabled;
|
||||||
CT.TunnelFloorTerraceStepHeight = Params.TunnelFloorTerraceStepHeight;
|
CT.TunnelFloorTerraceStepHeight = Params.TunnelFloorTerraceStepHeight;
|
||||||
CT.TunnelFloorMaxLedgeHeight = Params.TunnelFloorMaxLedgeHeight;
|
CT.TunnelFloorMaxLedgeHeight = Params.TunnelFloorMaxLedgeHeight;
|
||||||
@@ -7067,8 +6905,7 @@ void VoxelCaveMorphology::BuildChunkCache(
|
|||||||
const uint32 LedgeChoiceHash = VoxelHash::Mix(
|
const uint32 LedgeChoiceHash = VoxelHash::Mix(
|
||||||
TunnelHash ^ 0xC1A0E5EDu);
|
TunnelHash ^ 0xC1A0E5EDu);
|
||||||
constexpr float DramaticLedgeChance = 0.35f;
|
constexpr float DramaticLedgeChance = 0.35f;
|
||||||
const bool bDramaticLedge = Params.bTunnelFloorEnabled
|
const bool bDramaticLedge = Params.bTunnelFloorTerracingEnabled
|
||||||
&& Params.bTunnelFloorTerracingEnabled
|
|
||||||
&& bDirectSteep
|
&& bDirectSteep
|
||||||
&& bGraphLedgeEligible
|
&& bGraphLedgeEligible
|
||||||
&& VoxelHash::ToFloat01(LedgeChoiceHash) < DramaticLedgeChance;
|
&& VoxelHash::ToFloat01(LedgeChoiceHash) < DramaticLedgeChance;
|
||||||
@@ -7116,8 +6953,7 @@ void VoxelCaveMorphology::BuildChunkCache(
|
|||||||
// supplies the horizontal run for the authored floor plane and keeps the room-to-room
|
// supplies the horizontal run for the authored floor plane and keeps the room-to-room
|
||||||
// graph connected. The rings observed in the owner asset came from the separate
|
// graph connected. The rings observed in the owner asset came from the separate
|
||||||
// world-core handoff exposing span joins, not from this smooth route decision.
|
// world-core handoff exposing span joins, not from this smooth route decision.
|
||||||
const bool bNeedsWind = Params.bTunnelFloorEnabled
|
const bool bNeedsWind = bDirectSteep
|
||||||
&& bDirectSteep
|
|
||||||
&& !bDramaticLedge
|
&& !bDramaticLedge
|
||||||
&& GentleThreshold > KINDA_SMALL_NUMBER;
|
&& GentleThreshold > KINDA_SMALL_NUMBER;
|
||||||
CT.bFloorRouteWasWound = bNeedsWind;
|
CT.bFloorRouteWasWound = bNeedsWind;
|
||||||
@@ -7984,7 +7820,8 @@ float VoxelCaveMorphology::EvaluateSDFCached(
|
|||||||
const FChunkSDFCache& Cache,
|
const FChunkSDFCache& Cache,
|
||||||
float SDFBlendRadius,
|
float SDFBlendRadius,
|
||||||
int32* OutNearestRoomIdx,
|
int32* OutNearestRoomIdx,
|
||||||
bool bUseSpatialIndex)
|
bool bUseSpatialIndex,
|
||||||
|
const FVector* WorldTunnelPosition)
|
||||||
{
|
{
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::RoomGraphSdf);
|
VoxelDensityProfile::EBucket::RoomGraphSdf);
|
||||||
@@ -8144,20 +7981,41 @@ float VoxelCaveMorphology::EvaluateSDFCached(
|
|||||||
//=========================================================================
|
//=========================================================================
|
||||||
// Tunnel SDFs
|
// Tunnel SDFs
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
|
// Keep the tunnel source in the authored world frame when the caller supplies it. Rooms
|
||||||
|
// intentionally remain at the warped query position; the world-chain tunnel is the same
|
||||||
|
// continuous field used by the core hand-off below, so the source cannot form a second,
|
||||||
|
// differently-shaped surface around the route.
|
||||||
|
const bool bUseWorldTunnel = WorldTunnelPosition != nullptr;
|
||||||
|
const FVector TunnelPosition = bUseWorldTunnel ? *WorldTunnelPosition : Pos;
|
||||||
auto EvaluateTunnel = [&](int32 TunnelIdx)
|
auto EvaluateTunnel = [&](int32 TunnelIdx)
|
||||||
{
|
{
|
||||||
const FCachedTunnel& Tunnel = TunnelData[TunnelIdx];
|
const FCachedTunnel& Tunnel = TunnelData[TunnelIdx];
|
||||||
if (Tunnel.SDFInfluenceRadius > 0.0f
|
const bool bHasWorldChain = Tunnel.WorldControlPoints.Num() >= 2
|
||||||
|
&& Tunnel.WorldControlRadii.Num() == Tunnel.WorldControlPoints.Num();
|
||||||
|
const bool bUseWorldChain = bUseWorldTunnel && bHasWorldChain;
|
||||||
|
const FVector& QueryPosition = bUseWorldChain ? TunnelPosition : Pos;
|
||||||
|
const FVector& CenterlineMin = bUseWorldChain
|
||||||
|
? Tunnel.WorldCenterlineMin : Tunnel.SDFCenterlineMin;
|
||||||
|
const FVector& CenterlineMax = bUseWorldChain
|
||||||
|
? Tunnel.WorldCenterlineMax : Tunnel.SDFCenterlineMax;
|
||||||
|
const float InfluenceRadius = bUseWorldChain
|
||||||
|
? Tunnel.WorldInfluenceRadius : Tunnel.SDFInfluenceRadius;
|
||||||
|
const FVector& BoundCenter = bUseWorldChain
|
||||||
|
? Tunnel.WorldBoundCenter : Tunnel.BoundCenter;
|
||||||
|
const float BoundRadiusSq = bUseWorldChain
|
||||||
|
? Tunnel.WorldBoundRadiusSq : Tunnel.BoundRadiusSq;
|
||||||
|
if (InfluenceRadius > 0.0f
|
||||||
&& VF_DistanceSquaredToAabb(
|
&& VF_DistanceSquaredToAabb(
|
||||||
WorldX, WorldY, WorldZ,
|
QueryPosition.X, QueryPosition.Y, QueryPosition.Z,
|
||||||
Tunnel.SDFCenterlineMin, Tunnel.SDFCenterlineMax)
|
CenterlineMin, CenterlineMax)
|
||||||
> FMath::Square(Tunnel.SDFInfluenceRadius))
|
> FMath::Square(InfluenceRadius))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// --- BOUNDING SPHERE CULL ---
|
// --- BOUNDING SPHERE CULL ---
|
||||||
const float DistSq = VF_FloatDistSquared(WorldX, WorldY, WorldZ, Tunnel.BoundCenter);
|
const float DistSq = VF_FloatDistSquared(
|
||||||
if (DistSq > Tunnel.BoundRadiusSq) return;
|
QueryPosition.X, QueryPosition.Y, QueryPosition.Z, BoundCenter);
|
||||||
|
if (DistSq > BoundRadiusSq) return;
|
||||||
if (VoxelDensityProfile::AreCountersEnabled())
|
if (VoxelDensityProfile::AreCountersEnabled())
|
||||||
{
|
{
|
||||||
VoxelDensityProfile::AddCounter(
|
VoxelDensityProfile::AddCounter(
|
||||||
@@ -8168,11 +8026,12 @@ float VoxelCaveMorphology::EvaluateSDFCached(
|
|||||||
// its build-time floor profile before the tunnel union is blended into rooms. The room's
|
// its build-time floor profile before the tunnel union is blended into rooms. The room's
|
||||||
// finite mouth ownership is also passed into that same shape evaluation; this is the SDF
|
// finite mouth ownership is also passed into that same shape evaluation; this is the SDF
|
||||||
// side of A > B, while the world-space post keeps the disturbance path consistent.
|
// side of A > B, while the world-space post keeps the disturbance path consistent.
|
||||||
const FVFRoomFloorOwnership RoomMouthOwnership =
|
const FVFRoomFloorOwnership RoomMouthOwnership = bUseWorldChain
|
||||||
VF_FindSDFTunnelMouthOwnership(Pos, Tunnel, BlendK);
|
? VF_FindWorldTunnelMouthOwnership(QueryPosition, Tunnel, BlendK)
|
||||||
|
: VF_FindSDFTunnelMouthOwnership(Pos, Tunnel, BlendK);
|
||||||
const float TunnelSDF = VF_EvaluateSweptTunnel(
|
const float TunnelSDF = VF_EvaluateSweptTunnel(
|
||||||
Pos, Tunnel, /*bWorldChain=*/false, BlendK,
|
QueryPosition, Tunnel, bUseWorldChain, BlendK,
|
||||||
/*bApplyFloorCut=*/Tunnel.bTunnelFloorEnabled, &Cache, TunnelIdx,
|
/*bApplyFloorCut=*/true, &Cache, TunnelIdx,
|
||||||
&RoomMouthOwnership).SDF;
|
&RoomMouthOwnership).SDF;
|
||||||
|
|
||||||
MinSDF = VoxelSDF::SmoothMin(MinSDF, TunnelSDF, BlendK);
|
MinSDF = VoxelSDF::SmoothMin(MinSDF, TunnelSDF, BlendK);
|
||||||
@@ -8182,7 +8041,8 @@ float VoxelCaveMorphology::EvaluateSDFCached(
|
|||||||
if (!VoxelDensityAblation::IsTunnelSDFOff())
|
if (!VoxelDensityAblation::IsTunnelSDFOff())
|
||||||
{
|
{
|
||||||
TunnelCandidateCount = VF_ForEachSpatialCandidate(
|
TunnelCandidateCount = VF_ForEachSpatialCandidate(
|
||||||
Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), WorldX, WorldY, EvaluateTunnel,
|
Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), TunnelPosition.X, TunnelPosition.Y,
|
||||||
|
EvaluateTunnel,
|
||||||
bUseSpatialIndex);
|
bUseSpatialIndex);
|
||||||
}
|
}
|
||||||
if (VoxelDensityProfile::AreCountersEnabled())
|
if (VoxelDensityProfile::AreCountersEnabled())
|
||||||
@@ -8260,7 +8120,14 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
|||||||
// GenerateMesh installs the exact tile/block reach mask after building the complete tile
|
// GenerateMesh installs the exact tile/block reach mask after building the complete tile
|
||||||
// cache. Point queries retain the canonical all-reachable TLS default; debug proof calls set
|
// cache. Point queries retain the canonical all-reachable TLS default; debug proof calls set
|
||||||
// the bypass bit so the same sample can be evaluated as the reference field.
|
// the bypass bit so the same sample can be evaluated as the reference field.
|
||||||
if (!VoxelGenLOD::IsTilePostReachBypassActive()
|
// A reach mask is meaningful only inside the mesher's proven tile session. Point queries
|
||||||
|
// (including the owner-value probe and gameplay queries) must retain the canonical core even
|
||||||
|
// if another caller left a conservative flag in this worker's TLS. The mesher enables the
|
||||||
|
// proof bit only after it has installed the exact tile-window decision, so this guard cannot
|
||||||
|
// turn an ordinary query into a skipped core.
|
||||||
|
if (VoxelGenLOD::bTileCoreReachProofEnabled
|
||||||
|
&& VoxelGenLOD::bTileReachDiagnosticTile
|
||||||
|
&& !VoxelGenLOD::IsTilePostReachBypassActive()
|
||||||
&& !VoxelGenLOD::IsTunnelCoreReachable())
|
&& !VoxelGenLOD::IsTunnelCoreReachable())
|
||||||
{
|
{
|
||||||
return FTunnelCoreWorldEvaluation();
|
return FTunnelCoreWorldEvaluation();
|
||||||
@@ -8328,73 +8195,23 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
|||||||
|
|
||||||
const FVFRoomFloorOwnership RoomMouthOwnership =
|
const FVFRoomFloorOwnership RoomMouthOwnership =
|
||||||
VF_FindWorldTunnelMouthOwnership(Pos, Tunnel, Cache.SDFBlendRadius);
|
VF_FindWorldTunnelMouthOwnership(Pos, Tunnel, Cache.SDFBlendRadius);
|
||||||
const bool bRoomOwnsThisTunnel = RoomMouthOwnership.bValid;
|
// The world chain is the authored tunnel shape. Its floor is evaluated by the same
|
||||||
bool bRaisedTunnelFloor = false;
|
// SmoothMax profile as the cave-warped chain; there is no second support-column field and
|
||||||
// The world chain is used only for its finite support-floor ownership band. Its air
|
// no hard floor hand-off at a segment boundary. Keep the legacy argument source-compatible
|
||||||
// surface follows a continuous sweep, while the ordinary cave-warped chain remains the
|
// for callers compiled against the previous probe API, but deliberately ignore it.
|
||||||
// authoritative carved tunnel. Evaluating a second per-span capsule union creates a
|
(void)SupportColumn;
|
||||||
// segmented zero set at every control-span join.
|
|
||||||
if (bHasWorldChain && Tunnel.bTunnelFloorEnabled)
|
|
||||||
{
|
|
||||||
if (SupportColumn != nullptr && VoxelDensityProfile::AreCountersEnabled())
|
|
||||||
{
|
|
||||||
VoxelDensityProfile::AddCounter(
|
|
||||||
VoxelDensityProfile::ECounter::TunnelSupportFloorChecks);
|
|
||||||
}
|
|
||||||
float FloorZ = 0.0f;
|
|
||||||
float SupportRadius = 0.0f;
|
|
||||||
float SupportMinZ = 0.0f;
|
|
||||||
float SupportMaxZ = 0.0f;
|
|
||||||
const bool bProjectedFloor = SupportColumn != nullptr
|
|
||||||
? VoxelCaveMorphology::GetTunnelSupportFloorColumnBand(
|
|
||||||
TunnelIdx, *SupportColumn, FloorZ, SupportMinZ, SupportMaxZ)
|
|
||||||
: VF_ProjectSweptTunnelFloor(
|
|
||||||
Pos, Tunnel, /*bWorldChain=*/true, FloorZ, SupportRadius,
|
|
||||||
&Cache, TunnelIdx, &RoomMouthOwnership);
|
|
||||||
bRaisedTunnelFloor = bRoomOwnsThisTunnel
|
|
||||||
&& bProjectedFloor
|
|
||||||
&& FloorZ > RoomMouthOwnership.FloorZ
|
|
||||||
+ KINDA_SMALL_NUMBER;
|
|
||||||
if (bRaisedTunnelFloor
|
|
||||||
&& WorldZ <= RoomMouthOwnership.FloorZ
|
|
||||||
+ VoxelPassageGeometry::WalkableTunnelFloorAirClearanceVoxels)
|
|
||||||
{
|
|
||||||
if (WorldZ > RoomMouthOwnership.FloorZ
|
|
||||||
- VoxelPassageGeometry::LandingFloorThicknessVoxels)
|
|
||||||
{
|
|
||||||
Result.bRoomFloor = true;
|
|
||||||
}
|
|
||||||
// A raised tunnel apron is the artifact being clipped. Keep the room floor in
|
|
||||||
// its finite support band and do not reopen the room below it.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!bRaisedTunnelFloor
|
|
||||||
&& bProjectedFloor
|
|
||||||
&& WorldZ <= FloorZ
|
|
||||||
+ VoxelPassageGeometry::WalkableTunnelFloorAirClearanceVoxels
|
|
||||||
&& (SupportColumn == nullptr
|
|
||||||
? WorldZ >= FloorZ
|
|
||||||
- VoxelPassageGeometry::LandingFloorThicknessVoxels
|
|
||||||
: WorldZ >= SupportMinZ && WorldZ <= SupportMaxZ))
|
|
||||||
{
|
|
||||||
Result.bSupportFloor = true;
|
|
||||||
}
|
|
||||||
if (!bRaisedTunnelFloor
|
|
||||||
&& bProjectedFloor && WorldZ <= FloorZ
|
|
||||||
+ VoxelPassageGeometry::WalkableTunnelFloorAirClearanceVoxels)
|
|
||||||
{
|
|
||||||
// Preserve the established ownership contract while the SDF-space evaluator is
|
|
||||||
// still raw: the floor owns the lower half of the corridor, and the capsule owns
|
|
||||||
// only the air above it. This is a shape-level ownership hand-off, not a later slab.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const FVFTunnelShapeEvaluation TunnelShape = VF_EvaluateSweptTunnel(
|
const FVFTunnelShapeEvaluation TunnelShape = VF_EvaluateSweptTunnel(
|
||||||
Pos, Tunnel, bHasWorldChain, Cache.SDFBlendRadius,
|
Pos, Tunnel, bHasWorldChain, Cache.SDFBlendRadius,
|
||||||
/*bApplyFloorCut=*/Tunnel.bTunnelFloorEnabled && !bRaisedTunnelFloor,
|
/*bApplyFloorCut=*/true,
|
||||||
&Cache, TunnelIdx, &RoomMouthOwnership);
|
&Cache, TunnelIdx, &RoomMouthOwnership);
|
||||||
Result.SDF = FMath::Min(Result.SDF, TunnelShape.SDF);
|
if (TunnelShape.SDF < Result.SDF)
|
||||||
|
{
|
||||||
|
Result.SDF = TunnelShape.SDF;
|
||||||
|
Result.bHasSweptFloor = TunnelShape.bHasSweptFloor;
|
||||||
|
Result.SweptFloorZ = TunnelShape.SweptFloorZ;
|
||||||
|
Result.SweptFloorRadius = TunnelShape.SweptFloorRadius;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const int32 TunnelCandidateCount = VF_ForEachSpatialCandidate(
|
const int32 TunnelCandidateCount = VF_ForEachSpatialCandidate(
|
||||||
@@ -8604,8 +8421,7 @@ bool VoxelCaveMorphology::AnyTunnelCoreWorldNearLattice(
|
|||||||
float ProfileFloorDelta = 0.0f;
|
float ProfileFloorDelta = 0.0f;
|
||||||
const TArray<FTunnelFloorSegmentProfile>& Profiles = bHasWorldChain
|
const TArray<FTunnelFloorSegmentProfile>& Profiles = bHasWorldChain
|
||||||
? Tunnel.WorldFloorProfiles : Tunnel.FloorProfiles;
|
? Tunnel.WorldFloorProfiles : Tunnel.FloorProfiles;
|
||||||
if (Tunnel.bTunnelFloorEnabled
|
if (Profiles.Num() == ChainPoints.Num() - 1)
|
||||||
&& Profiles.Num() == ChainPoints.Num() - 1)
|
|
||||||
{
|
{
|
||||||
for (int32 SegmentIndex = 0;
|
for (int32 SegmentIndex = 0;
|
||||||
SegmentIndex < Profiles.Num(); ++SegmentIndex)
|
SegmentIndex < Profiles.Num(); ++SegmentIndex)
|
||||||
@@ -8632,11 +8448,9 @@ bool VoxelCaveMorphology::AnyTunnelCoreWorldNearLattice(
|
|||||||
}
|
}
|
||||||
if (bHasTightChain)
|
if (bHasTightChain)
|
||||||
{
|
{
|
||||||
const float FloorPad = Tunnel.bTunnelFloorEnabled
|
const float FloorPad = FMath::Max(
|
||||||
? FMath::Max(
|
|
||||||
VoxelPassageGeometry::LandingFloorThicknessVoxels,
|
VoxelPassageGeometry::LandingFloorThicknessVoxels,
|
||||||
FMath::Max(Cache.SDFBlendRadius, 0.0f) * 0.35f)
|
FMath::Max(Cache.SDFBlendRadius, 0.0f) * 0.35f);
|
||||||
: 0.0f;
|
|
||||||
const float SegmentPad = MaxRadius + FloorPad
|
const float SegmentPad = MaxRadius + FloorPad
|
||||||
+ ProfileFloorDelta + ReliefBound;
|
+ ProfileFloorDelta + ReliefBound;
|
||||||
if (!VoxelMath::IsFinite(SegmentPad))
|
if (!VoxelMath::IsFinite(SegmentPad))
|
||||||
@@ -8667,8 +8481,8 @@ bool VoxelCaveMorphology::AnyTunnelCoreWorldNearLattice(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bTightTouch && Tunnel.bTunnelFloorEnabled
|
if (!bTightTouch && bHasWorldChain
|
||||||
&& bHasWorldChain && Tunnel.bHasFloorRoomOwnership)
|
&& Tunnel.bHasFloorRoomOwnership)
|
||||||
{
|
{
|
||||||
const float MouthHorizontal = MouthReach;
|
const float MouthHorizontal = MouthReach;
|
||||||
const float MouthVertical = FMath::Max(
|
const float MouthVertical = FMath::Max(
|
||||||
@@ -8792,7 +8606,7 @@ bool VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
|
|||||||
|
|
||||||
return VF_EvaluateSweptTunnel(
|
return VF_EvaluateSweptTunnel(
|
||||||
Pos, Tunnel, bHasWorldChain, Cache.SDFBlendRadius,
|
Pos, Tunnel, bHasWorldChain, Cache.SDFBlendRadius,
|
||||||
/*bApplyFloorCut=*/Tunnel.bTunnelFloorEnabled, &Cache, TunnelIdx,
|
/*bApplyFloorCut=*/true, &Cache, TunnelIdx,
|
||||||
&RoomMouthOwnership).bSupportFloor;
|
&RoomMouthOwnership).bSupportFloor;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3748,110 +3748,6 @@ namespace
|
|||||||
FVector LastTunnelCoreEvaluationPosition = FVector::ZeroVector;
|
FVector LastTunnelCoreEvaluationPosition = FVector::ZeroVector;
|
||||||
bool bLastTunnelCoreWorldEvaluationValid = false;
|
bool bLastTunnelCoreWorldEvaluationValid = false;
|
||||||
|
|
||||||
// Support-floor projection depends only on XY for one immutable SDF cache. The
|
|
||||||
// mesher traverses Z outside X/Y, so keep a small worker-local LRU of XY boxes rather
|
|
||||||
// than only the immediately previous column; each integer column is then projected
|
|
||||||
// once and both structural posts reuse its interval for every Z sample.
|
|
||||||
struct FSupportColumnBox
|
|
||||||
{
|
|
||||||
static constexpr int32 Halo = CHUNK_SIZE + 8;
|
|
||||||
static constexpr int32 Dim = 2 * Halo + 1;
|
|
||||||
|
|
||||||
const FChunkSDFCache* Cache = nullptr;
|
|
||||||
int32 BaseX = 0;
|
|
||||||
int32 BaseY = 0;
|
|
||||||
uint32 LastUse = 0;
|
|
||||||
bool bValid = false;
|
|
||||||
TArray<int32> SlotToEntry;
|
|
||||||
TArray<FTunnelSupportFloorColumn> Columns;
|
|
||||||
|
|
||||||
void Clear()
|
|
||||||
{
|
|
||||||
Cache = nullptr;
|
|
||||||
bValid = false;
|
|
||||||
LastUse = 0;
|
|
||||||
SlotToEntry.Reset();
|
|
||||||
Columns.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
FTunnelSupportFloorColumn* AcquireColumn(
|
|
||||||
int32 X, int32 Y, const FChunkSDFCache* InCache,
|
|
||||||
uint32 InUse, bool& bOutNew)
|
|
||||||
{
|
|
||||||
bOutNew = false;
|
|
||||||
if (!bValid || Cache != InCache
|
|
||||||
|| X < BaseX || X >= BaseX + Dim
|
|
||||||
|| Y < BaseY || Y >= BaseY + Dim)
|
|
||||||
{
|
|
||||||
Cache = InCache;
|
|
||||||
BaseX = X - Halo;
|
|
||||||
BaseY = Y - Halo;
|
|
||||||
LastUse = InUse;
|
|
||||||
bValid = true;
|
|
||||||
SlotToEntry.Init(INDEX_NONE, Dim * Dim);
|
|
||||||
Columns.Reset();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LastUse = InUse;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int32 Slot = (Y - BaseY) * Dim + (X - BaseX);
|
|
||||||
int32& EntryIndex = SlotToEntry[Slot];
|
|
||||||
if (EntryIndex == INDEX_NONE)
|
|
||||||
{
|
|
||||||
EntryIndex = Columns.AddDefaulted();
|
|
||||||
bOutNew = true;
|
|
||||||
}
|
|
||||||
return &Columns[EntryIndex];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FSupportColumnCache
|
|
||||||
{
|
|
||||||
static constexpr int32 NumBoxes = 4;
|
|
||||||
FSupportColumnBox Boxes[NumBoxes];
|
|
||||||
uint32 Clock = 0;
|
|
||||||
|
|
||||||
void Invalidate()
|
|
||||||
{
|
|
||||||
for (FSupportColumnBox& Box : Boxes)
|
|
||||||
{
|
|
||||||
Box.Clear();
|
|
||||||
}
|
|
||||||
Clock = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
FSupportColumnBox& AcquireBox(
|
|
||||||
int32 X, int32 Y, const FChunkSDFCache* Cache)
|
|
||||||
{
|
|
||||||
++Clock;
|
|
||||||
for (FSupportColumnBox& Box : Boxes)
|
|
||||||
{
|
|
||||||
if (Box.bValid && Box.Cache == Cache
|
|
||||||
&& X >= Box.BaseX && X < Box.BaseX + FSupportColumnBox::Dim
|
|
||||||
&& Y >= Box.BaseY && Y < Box.BaseY + FSupportColumnBox::Dim)
|
|
||||||
{
|
|
||||||
Box.LastUse = Clock;
|
|
||||||
return Box;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FSupportColumnBox* Victim = &Boxes[0];
|
|
||||||
for (FSupportColumnBox& Box : Boxes)
|
|
||||||
{
|
|
||||||
if (!Box.bValid || Box.LastUse < Victim->LastUse)
|
|
||||||
{
|
|
||||||
Victim = &Box;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Victim->Clear();
|
|
||||||
return *Victim;
|
|
||||||
}
|
|
||||||
} SupportColumns;
|
|
||||||
|
|
||||||
FTunnelSupportFloorColumn FractionalSupportColumn;
|
|
||||||
|
|
||||||
/** ÉTAPE C1 — les params de la strate avec l'op de CETTE salle appliqué par-dessus.
|
/** ÉTAPE C1 — les params de la strate avec l'op de CETTE salle appliqué par-dessus.
|
||||||
* Mémo par voxel : invalidé au début de chaque `Eval`, calculé au PREMIER modificateur
|
* Mémo par voxel : invalidé au début de chaque `Eval`, calculé au PREMIER modificateur
|
||||||
* qui le demande. C'est ce qui reproduit le coût de l'original (une copie de struct par
|
* qui le demande. C'est ce qui reproduit le coût de l'original (une copie de struct par
|
||||||
@@ -3906,49 +3802,6 @@ namespace
|
|||||||
S.bLocalParamsValid = false;
|
S.bLocalParamsValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BuildSupportColumn(
|
|
||||||
float WorldX, float WorldY,
|
|
||||||
const FChunkSDFCache& Cache,
|
|
||||||
FTunnelSupportFloorColumn& OutColumn) const
|
|
||||||
{
|
|
||||||
VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
|
|
||||||
WorldX, WorldY, Cache, OutColumn,
|
|
||||||
VoxelGenLOD::ShouldUseSpatialIndex(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
const FTunnelSupportFloorColumn* GetSupportColumn(const FVector& Position) const
|
|
||||||
{
|
|
||||||
FState& S = State();
|
|
||||||
const bool bIntegerXY =
|
|
||||||
Position.X == FMath::FloorToFloat(static_cast<float>(Position.X))
|
|
||||||
&& Position.Y == FMath::FloorToFloat(static_cast<float>(Position.Y));
|
|
||||||
if (bIntegerXY)
|
|
||||||
{
|
|
||||||
const int32 IX = FMath::FloorToInt(static_cast<float>(Position.X));
|
|
||||||
const int32 IY = FMath::FloorToInt(static_cast<float>(Position.Y));
|
|
||||||
const FChunkSDFCache& Cache = GetCache();
|
|
||||||
FState::FSupportColumnBox& Box = S.SupportColumns.AcquireBox(
|
|
||||||
IX, IY, &Cache);
|
|
||||||
bool bNewColumn = false;
|
|
||||||
FTunnelSupportFloorColumn* Column = Box.AcquireColumn(
|
|
||||||
IX, IY, &Cache, S.SupportColumns.Clock, bNewColumn);
|
|
||||||
if (bNewColumn)
|
|
||||||
{
|
|
||||||
BuildSupportColumn(
|
|
||||||
static_cast<float>(Position.X),
|
|
||||||
static_cast<float>(Position.Y),
|
|
||||||
Cache,
|
|
||||||
*Column);
|
|
||||||
}
|
|
||||||
return Column;
|
|
||||||
}
|
|
||||||
|
|
||||||
BuildSupportColumn(
|
|
||||||
static_cast<float>(Position.X), static_cast<float>(Position.Y),
|
|
||||||
GetCache(), S.FractionalSupportColumn);
|
|
||||||
return &S.FractionalSupportColumn;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TryGetLastTunnelCoreWorldEvaluation(
|
bool TryGetLastTunnelCoreWorldEvaluation(
|
||||||
FTunnelCoreWorldEvaluation& OutEvaluation) const override
|
FTunnelCoreWorldEvaluation& OutEvaluation) const override
|
||||||
{
|
{
|
||||||
@@ -3971,9 +3824,8 @@ namespace
|
|||||||
// demand, publish the one combined result here so the tail can reuse it without
|
// demand, publish the one combined result here so the tail can reuse it without
|
||||||
// building/scanning a second native cache.
|
// building/scanning a second native cache.
|
||||||
const FVector& Position = S.LastWorldPosition;
|
const FVector& Position = S.LastWorldPosition;
|
||||||
const FTunnelSupportFloorColumn* SupportColumn = GetSupportColumn(Position);
|
|
||||||
S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||||
Position.X, Position.Y, Position.Z, GetCache(), SupportColumn,
|
Position.X, Position.Y, Position.Z, GetCache(), nullptr,
|
||||||
VoxelGenLOD::ShouldUseSpatialIndex(false));
|
VoxelGenLOD::ShouldUseSpatialIndex(false));
|
||||||
S.LastTunnelCoreEvaluationPosition = Position;
|
S.LastTunnelCoreEvaluationPosition = Position;
|
||||||
S.bLastTunnelCoreWorldEvaluationValid = true;
|
S.bLastTunnelCoreWorldEvaluationValid = true;
|
||||||
@@ -4093,7 +3945,6 @@ namespace
|
|||||||
S.Cache = FChunkSDFCache();
|
S.Cache = FChunkSDFCache();
|
||||||
S.SharedCacheEntry.Reset();
|
S.SharedCacheEntry.Reset();
|
||||||
S.ActiveCache = &S.Cache;
|
S.ActiveCache = &S.Cache;
|
||||||
S.SupportColumns.Invalidate();
|
|
||||||
InOut.Sdf = FLT_MAX;
|
InOut.Sdf = FLT_MAX;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -4102,7 +3953,6 @@ namespace
|
|||||||
{
|
{
|
||||||
S.SharedCacheEntry.Reset();
|
S.SharedCacheEntry.Reset();
|
||||||
S.ActiveCache = &S.Cache;
|
S.ActiveCache = &S.Cache;
|
||||||
S.SupportColumns.Invalidate();
|
|
||||||
return; // Sdf reste FLT_MAX
|
return; // Sdf reste FLT_MAX
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4258,8 +4108,6 @@ namespace
|
|||||||
+ (bUseTileCacheWindow ? static_cast<float>(SampleStep) : 0.0f)
|
+ (bUseTileCacheWindow ? static_cast<float>(SampleStep) : 0.0f)
|
||||||
+ 2.0f;
|
+ 2.0f;
|
||||||
|
|
||||||
const FChunkSDFCache* PreviousActiveCache = S.ActiveCache;
|
|
||||||
bool bRebuiltLocalCache = false;
|
|
||||||
if (SampleStep > 1)
|
if (SampleStep > 1)
|
||||||
{
|
{
|
||||||
const bool bSharedCacheMatches =
|
const bool bSharedCacheMatches =
|
||||||
@@ -4308,7 +4156,6 @@ namespace
|
|||||||
&& LocalCache->RegionSize == RegionSize;
|
&& LocalCache->RegionSize == RegionSize;
|
||||||
if (!bCacheMatches)
|
if (!bCacheMatches)
|
||||||
{
|
{
|
||||||
bRebuiltLocalCache = true;
|
|
||||||
LocalCache->bValid = false;
|
LocalCache->bValid = false;
|
||||||
LocalCache->Seed = SeedU;
|
LocalCache->Seed = SeedU;
|
||||||
LocalCache->StrateIndex = StrateIdx;
|
LocalCache->StrateIndex = StrateIdx;
|
||||||
@@ -4332,15 +4179,11 @@ namespace
|
|||||||
}
|
}
|
||||||
S.ActiveCache = &LocalCache->Cache;
|
S.ActiveCache = &LocalCache->Cache;
|
||||||
}
|
}
|
||||||
if (bRebuiltLocalCache || S.ActiveCache != PreviousActiveCache)
|
|
||||||
{
|
|
||||||
S.SupportColumns.Invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool bUseSpatialIndex = VoxelGenLOD::ShouldUseSpatialIndex(false);
|
const bool bUseSpatialIndex = VoxelGenLOD::ShouldUseSpatialIndex(false);
|
||||||
|
const FVector WorldTunnelPosition(WorldX, WorldY, WorldZ);
|
||||||
float CaveSDF = VoxelCaveMorphology::EvaluateSDFCached(
|
float CaveSDF = VoxelCaveMorphology::EvaluateSDFCached(
|
||||||
WarpedX, WarpedY, WarpedZ, GetCache(), P.SDFBlendRadius,
|
WarpedX, WarpedY, WarpedZ, GetCache(), P.SDFBlendRadius,
|
||||||
&S.NearestRoom, bUseSpatialIndex);
|
&S.NearestRoom, bUseSpatialIndex, &WorldTunnelPosition);
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// PITS & CHEMINÉES — coordonnées RÉELLES, SmoothMin dans le même canal SDF
|
// PITS & CHEMINÉES — coordonnées RÉELLES, SmoothMin dans le même canal SDF
|
||||||
@@ -4424,45 +4267,31 @@ namespace
|
|||||||
|
|
||||||
InOut.Sdf = CaveSDF;
|
InOut.Sdf = CaveSDF;
|
||||||
|
|
||||||
// The swept floor is part of the cached tunnel shape, but the common structural tail
|
// Publish the same continuous tunnel-core field that the common generator tail will
|
||||||
// used to discover the same band only after all detail ops had run. Publish that
|
// reassert after generic writers. The stack does not publish or protect a separate
|
||||||
// build-time ownership now so the stack can preserve the authored support through its
|
// support slab.
|
||||||
// remaining modifiers without a second floor-writing pass. TryGet... below reuses the
|
|
||||||
// exact same cached result for the generator's post-disturbance hand-off.
|
|
||||||
if (!VoxelDensityAblation::IsTunnelCoreOff())
|
if (!VoxelDensityAblation::IsTunnelCoreOff())
|
||||||
{
|
{
|
||||||
const FTunnelSupportFloorColumn* SupportColumn = GetSupportColumn(
|
|
||||||
S.LastWorldPosition);
|
|
||||||
S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||||
WorldX, WorldY, WorldZ, GetCache(), SupportColumn,
|
WorldX, WorldY, WorldZ, GetCache(), nullptr,
|
||||||
bUseSpatialIndex);
|
bUseSpatialIndex);
|
||||||
S.LastTunnelCoreEvaluationPosition = S.LastWorldPosition;
|
S.LastTunnelCoreEvaluationPosition = S.LastWorldPosition;
|
||||||
S.bLastTunnelCoreWorldEvaluationValid = true;
|
S.bLastTunnelCoreWorldEvaluationValid = true;
|
||||||
if (S.LastTunnelCoreWorldEvaluation.bSupportFloor
|
|
||||||
|| S.LastTunnelCoreWorldEvaluation.bRoomFloor)
|
|
||||||
{
|
|
||||||
const float StructuralFloorMinimumDensity =
|
|
||||||
FMath::Max(P.BaseDensity * 2.0f, 1.0f);
|
|
||||||
if (VoxelMath::IsFinite(StructuralFloorMinimumDensity)
|
|
||||||
&& StructuralFloorMinimumDensity > 0.0f)
|
|
||||||
{
|
|
||||||
InOut.bProtectedStructuralFloor = true;
|
|
||||||
InOut.StructuralFloorMinimumDensity = StructuralFloorMinimumDensity;
|
|
||||||
if (VoxelDensityProfile::AreCountersEnabled())
|
|
||||||
{
|
|
||||||
VoxelDensityProfile::AddCounter(
|
|
||||||
VoxelDensityProfile::ECounter::TunnelAuthoredFloorSamples);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are the per-sample hand-offs needed by an op-major block. The scalar path
|
// Keep the hand-off for block evaluation compatibility. Both floor flags are
|
||||||
// continues to use FState directly; publishing them here only makes the dependency
|
// permanently false; the authored floor is part of the tunnel shape itself.
|
||||||
// explicit for a later modifier and does not alter Density/Sdf.
|
|
||||||
InOut.bHasTunnelCoreWorldEvaluation = true;
|
InOut.bHasTunnelCoreWorldEvaluation = true;
|
||||||
InOut.TunnelCoreWorldSDF = S.LastTunnelCoreWorldEvaluation.SDF;
|
InOut.TunnelCoreWorldSDF = S.LastTunnelCoreWorldEvaluation.SDF;
|
||||||
InOut.bTunnelCoreSupportFloor = S.LastTunnelCoreWorldEvaluation.bSupportFloor;
|
InOut.bTunnelCoreSupportFloor = false;
|
||||||
InOut.bTunnelCoreRoomFloor = S.LastTunnelCoreWorldEvaluation.bRoomFloor;
|
InOut.bTunnelCoreRoomFloor = false;
|
||||||
|
InOut.bHasTunnelCoreSweptFloor =
|
||||||
|
S.LastTunnelCoreWorldEvaluation.bHasSweptFloor;
|
||||||
|
InOut.TunnelCoreSweptFloorZ =
|
||||||
|
S.LastTunnelCoreWorldEvaluation.SweptFloorZ;
|
||||||
|
InOut.TunnelCoreSweptFloorRadius =
|
||||||
|
S.LastTunnelCoreWorldEvaluation.SweptFloorRadius;
|
||||||
|
InOut.bProtectedStructuralFloor = false;
|
||||||
|
InOut.StructuralFloorMinimumDensity = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// These cache hand-offs remain valid when tunnel core is off; detail operators still
|
// These cache hand-offs remain valid when tunnel core is off; detail operators still
|
||||||
@@ -6384,8 +6213,8 @@ namespace
|
|||||||
// A post-air sample must lie within a tapered capsule's maximum radius of one
|
// A post-air sample must lie within a tapered capsule's maximum radius of one
|
||||||
// world-chain segment. The segment AABB expanded by that radius is a cheap
|
// world-chain segment. The segment AABB expanded by that radius is a cheap
|
||||||
// superset of the exact capsule; unlike the old enclosing sphere it rejects a
|
// superset of the exact capsule; unlike the old enclosing sphere it rejects a
|
||||||
// long, thin tunnel from most unrelated LOD0 boxes. Support uses its own
|
// long, thin tunnel from most unrelated LOD0 boxes. The floor is part of this
|
||||||
// projected-floor AABB, because AllAir is killed by the finite floor band.
|
// same continuous field, so no second support-band candidate is needed.
|
||||||
if (Ctx.bUseLatticeProof)
|
if (Ctx.bUseLatticeProof)
|
||||||
{
|
{
|
||||||
const float WorldLatticeOriginX =
|
const float WorldLatticeOriginX =
|
||||||
@@ -6441,53 +6270,8 @@ namespace
|
|||||||
Result |= 1u;
|
Result |= 1u;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bWorldChain)
|
(void)bWorldChain;
|
||||||
{
|
(void)SegmentIndex;
|
||||||
const float NaturalFloorA = VoxelPassageGeometry::TunnelFloorZ(
|
|
||||||
A, SafeRadiusA);
|
|
||||||
const float NaturalFloorB = VoxelPassageGeometry::TunnelFloorZ(
|
|
||||||
BPoint, SafeRadiusB);
|
|
||||||
float FloorMin = FMath::Min(NaturalFloorA, NaturalFloorB);
|
|
||||||
float FloorMax = FMath::Max(NaturalFloorA, NaturalFloorB);
|
|
||||||
const TArray<FTunnelFloorSegmentProfile>* FloorProfiles =
|
|
||||||
&Tunnel.WorldFloorProfiles;
|
|
||||||
if (FloorProfiles->Num() == Tunnel.WorldControlPoints.Num() - 1
|
|
||||||
&& SegmentIndex >= 0
|
|
||||||
&& SegmentIndex < FloorProfiles->Num())
|
|
||||||
{
|
|
||||||
// The interval proof must include an explicit ledge's vertical
|
|
||||||
// riser. The profile transition is evaluated at the segment
|
|
||||||
// boundary, so its endpoint interval is a conservative superset
|
|
||||||
// of both plateau sides and remains valid for the lattice proof.
|
|
||||||
const FTunnelFloorSegmentProfile& Profile =
|
|
||||||
(*FloorProfiles)[SegmentIndex];
|
|
||||||
FloorMin = FMath::Min(FloorMin, FMath::Min(
|
|
||||||
Profile.StartFloorZ, Profile.EndFloorZ));
|
|
||||||
FloorMax = FMath::Max(FloorMax, FMath::Max(
|
|
||||||
Profile.StartFloorZ, Profile.EndFloorZ));
|
|
||||||
}
|
|
||||||
const float SupportRadius = FMath::Max(
|
|
||||||
FMath::Min(SafeRadiusA, SafeRadiusB) - 0.5f,
|
|
||||||
VoxelPassageGeometry::PlayerRadiusVoxels);
|
|
||||||
if (!Finite(FloorMin) || !Finite(FloorMax)
|
|
||||||
|| !Finite(SupportRadius))
|
|
||||||
{
|
|
||||||
Result = 3u;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (TouchesLatticeAABB(
|
|
||||||
FMath::Min(A.X, BPoint.X) - SupportRadius,
|
|
||||||
FMath::Max(A.X, BPoint.X) + SupportRadius,
|
|
||||||
FMath::Min(A.Y, BPoint.Y) - SupportRadius,
|
|
||||||
FMath::Max(A.Y, BPoint.Y) + SupportRadius,
|
|
||||||
FloorMin
|
|
||||||
- VoxelPassageGeometry::LandingFloorThicknessVoxels,
|
|
||||||
FloorMax
|
|
||||||
+ VoxelPassageGeometry::WalkableTunnelFloorAirClearanceVoxels))
|
|
||||||
{
|
|
||||||
Result |= 2u;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (bHasWorldChain)
|
if (bHasWorldChain)
|
||||||
@@ -6607,8 +6391,6 @@ namespace
|
|||||||
const uint8 TailMask = MayHaveTunnelCoreTail(Tunnel);
|
const uint8 TailMask = MayHaveTunnelCoreTail(Tunnel);
|
||||||
B.bMayHaveTunnelCoreAir = B.bMayHaveTunnelCoreAir
|
B.bMayHaveTunnelCoreAir = B.bMayHaveTunnelCoreAir
|
||||||
|| (TailMask & 1u) != 0;
|
|| (TailMask & 1u) != 0;
|
||||||
B.bMayHaveTunnelSupportFloor = B.bMayHaveTunnelSupportFloor
|
|
||||||
|| (TailMask & 2u) != 0;
|
|
||||||
B.bMayHaveTunnelCoreTail = B.bMayHaveTunnelCoreTail
|
B.bMayHaveTunnelCoreTail = B.bMayHaveTunnelCoreTail
|
||||||
|| TailMask != 0u;
|
|| TailMask != 0u;
|
||||||
float MaxRadius = FMath::Max(
|
float MaxRadius = FMath::Max(
|
||||||
@@ -6750,18 +6532,15 @@ namespace
|
|||||||
CountThreshold(TunnelLowerNoWarp, B.HitTunnelsNoWarp);
|
CountThreshold(TunnelLowerNoWarp, B.HitTunnelsNoWarp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((B.bMayHaveTunnelCoreAir || B.bMayHaveTunnelSupportFloor)
|
if (B.bMayHaveTunnelCoreAir
|
||||||
&& bUseLatticeProof
|
&& bUseLatticeProof
|
||||||
&& ExactLatticeWorldQueries.Num() > 0)
|
&& ExactLatticeWorldQueries.Num() > 0)
|
||||||
{
|
{
|
||||||
// The bound test above is only a broad candidate. The actual post is a
|
// The bound test above is only a broad candidate. The actual post is a
|
||||||
// discrete writer: it opens a point when the world-core SDF is below the inset
|
// continuous writer: it opens a point when the world-core SDF is negative. This
|
||||||
// without a support floor, or makes it solid in the support-floor band. Keep the
|
// is still a proof of the final lattice, never a density shortcut; if an exact
|
||||||
// two polarities separate: an AllSolid box only needs air, while an AllAir box
|
// query is invalid, keep the conservative candidate.
|
||||||
// only needs support. This is still a proof of the final lattice, never a density
|
|
||||||
// shortcut; if an exact query is invalid, keep both conservative candidates.
|
|
||||||
bool bExactAir = false;
|
bool bExactAir = false;
|
||||||
bool bExactSupport = false;
|
|
||||||
bool bExactTailUnknown = false;
|
bool bExactTailUnknown = false;
|
||||||
TArray<FBox, TInlineAllocator<16>> ExactTailDomains;
|
TArray<FBox, TInlineAllocator<16>> ExactTailDomains;
|
||||||
bool bExactTailDomainCullSafe = true;
|
bool bExactTailDomainCullSafe = true;
|
||||||
@@ -6799,7 +6578,7 @@ namespace
|
|||||||
VoxelBox.Min, VoxelBox.Max,
|
VoxelBox.Min, VoxelBox.Max,
|
||||||
[&](const FVector&, const FVector& World)
|
[&](const FVector&, const FVector& World)
|
||||||
{
|
{
|
||||||
if ((bExactAir && bExactSupport) || bExactTailUnknown) return;
|
if (bExactAir || bExactTailUnknown) return;
|
||||||
++B.ExactTailQueries;
|
++B.ExactTailQueries;
|
||||||
if (bExactTailDomainCullSafe && ExactTailDomains.Num() > 0)
|
if (bExactTailDomainCullSafe && ExactTailDomains.Num() > 0)
|
||||||
{
|
{
|
||||||
@@ -6853,33 +6632,15 @@ namespace
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bExactSupport |= (State & 0x02u) != 0;
|
|
||||||
bExactAir |= (State & 0x01u) != 0;
|
bExactAir |= (State & 0x01u) != 0;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FTunnelSupportFloorColumn EmptySupportColumn;
|
|
||||||
const FTunnelSupportFloorColumn* SupportColumn = nullptr;
|
|
||||||
const bool bIntegerXY =
|
|
||||||
World.X == FMath::FloorToFloat(static_cast<float>(World.X))
|
|
||||||
&& World.Y == FMath::FloorToFloat(static_cast<float>(World.Y));
|
|
||||||
if (bIntegerXY)
|
|
||||||
{
|
|
||||||
SupportColumn = GetSupportColumn(World);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
|
|
||||||
static_cast<float>(World.X), static_cast<float>(World.Y),
|
|
||||||
GetBoxCache(), EmptySupportColumn,
|
|
||||||
VoxelGenLOD::ShouldUseSpatialIndex(false));
|
|
||||||
SupportColumn = &EmptySupportColumn;
|
|
||||||
}
|
|
||||||
const FTunnelCoreWorldEvaluation Evaluation =
|
const FTunnelCoreWorldEvaluation Evaluation =
|
||||||
VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||||
static_cast<float>(World.X), static_cast<float>(World.Y),
|
static_cast<float>(World.X), static_cast<float>(World.Y),
|
||||||
static_cast<float>(World.Z), GetBoxCache(), SupportColumn,
|
static_cast<float>(World.Z), GetBoxCache(), nullptr,
|
||||||
VoxelGenLOD::ShouldUseSpatialIndex(false));
|
VoxelGenLOD::ShouldUseSpatialIndex(false));
|
||||||
++B.ExactTailEvaluated;
|
++B.ExactTailEvaluated;
|
||||||
if (!VoxelMath::IsFinite(Evaluation.SDF))
|
if (!VoxelMath::IsFinite(Evaluation.SDF))
|
||||||
@@ -6889,15 +6650,12 @@ namespace
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const bool bAir = !Evaluation.bSupportFloor
|
const bool bAir = Evaluation.SDF < 0.0f;
|
||||||
&& Evaluation.SDF
|
|
||||||
< -VoxelPassageGeometry::CaveTunnelAirCoreInsetVoxels;
|
|
||||||
bExactSupport |= Evaluation.bSupportFloor;
|
|
||||||
bExactAir |= bAir;
|
bExactAir |= bAir;
|
||||||
if (CachedTailState != nullptr)
|
if (CachedTailState != nullptr)
|
||||||
{
|
{
|
||||||
*CachedTailState = static_cast<uint8>(
|
*CachedTailState = static_cast<uint8>(
|
||||||
0x80u | (Evaluation.bSupportFloor ? 0x02u : 0u)
|
0x80u
|
||||||
| (bAir ? 0x01u : 0u));
|
| (bAir ? 0x01u : 0u));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6906,8 +6664,8 @@ namespace
|
|||||||
if (!bExactTailUnknown)
|
if (!bExactTailUnknown)
|
||||||
{
|
{
|
||||||
B.bMayHaveTunnelCoreAir = bExactAir;
|
B.bMayHaveTunnelCoreAir = bExactAir;
|
||||||
B.bMayHaveTunnelSupportFloor = bExactSupport;
|
B.bMayHaveTunnelSupportFloor = false;
|
||||||
B.bMayHaveTunnelCoreTail = bExactAir || bExactSupport;
|
B.bMayHaveTunnelCoreTail = bExactAir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1021,8 +1021,7 @@ static FORCEINLINE void ApplyOriginLandingFloor(float& Density,
|
|||||||
// it works uniformly for every generator type. Stays inside the seal bands so it can
|
// it works uniformly for every generator type. Stays inside the seal bands so it can
|
||||||
// never breach a strate boundary. All features are hash-placed and deterministic.
|
// never breach a strate boundary. All features are hash-placed and deterministic.
|
||||||
static void ApplyDisturbances(float& MC, float X, float Y, float Z,
|
static void ApplyDisturbances(float& MC, float X, float Y, float Z,
|
||||||
const FStrateDisturbanceParams& D, uint32 Seed, bool bProtectVerticalShaftAir,
|
const FStrateDisturbanceParams& D, uint32 Seed, bool bProtectVerticalShaftAir)
|
||||||
bool bProtectAuthoredTunnelFloor)
|
|
||||||
{
|
{
|
||||||
if (VoxelDensityAblation::IsDisturbancesOff())
|
if (VoxelDensityAblation::IsDisturbancesOff())
|
||||||
{
|
{
|
||||||
@@ -1034,20 +1033,6 @@ static void ApplyDisturbances(float& MC, float X, float Y, float Z,
|
|||||||
const float InnerBot = D.StrateBottomWorldZ + D.BoundarySealThickness;
|
const float InnerBot = D.StrateBottomWorldZ + D.BoundarySealThickness;
|
||||||
if (Z <= InnerBot || Z >= InnerTop) return;
|
if (Z <= InnerBot || Z >= InnerTop) return;
|
||||||
|
|
||||||
// A tunnel floor is now authored into the cached shape with its clearance budget. The generic
|
|
||||||
// disturbance layer is deliberately outside that shape, so it must not reopen or bridge the
|
|
||||||
// finite support band after the shape has proved it. This is an ownership hand-off, not a
|
|
||||||
// per-sample natural-floor validation.
|
|
||||||
if (bProtectAuthoredTunnelFloor)
|
|
||||||
{
|
|
||||||
if (VoxelDensityProfile::AreCountersEnabled())
|
|
||||||
{
|
|
||||||
VoxelDensityProfile::AddCounter(
|
|
||||||
VoxelDensityProfile::ECounter::TunnelAuthoredFloorDisturbanceSkips);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float Solid = D.BaseDensity * 2.0f;
|
const float Solid = D.BaseDensity * 2.0f;
|
||||||
const float Blend = 3.0f;
|
const float Blend = 3.0f;
|
||||||
const FVector P(X, Y, Z);
|
const FVector P(X, Y, Z);
|
||||||
@@ -2301,7 +2286,6 @@ namespace
|
|||||||
uint64 ManagerLifetimeId = 0;
|
uint64 ManagerLifetimeId = 0;
|
||||||
FIntVector Chunk = FIntVector(INT32_MAX, INT32_MAX, INT32_MAX);
|
FIntVector Chunk = FIntVector(INT32_MAX, INT32_MAX, INT32_MAX);
|
||||||
FIntVector TileOrigin = FIntVector::ZeroValue;
|
FIntVector TileOrigin = FIntVector::ZeroValue;
|
||||||
FIntVector SupportChunk = FIntVector(INT32_MAX, INT32_MAX, INT32_MAX);
|
|
||||||
int32 TileStep = 0;
|
int32 TileStep = 0;
|
||||||
int32 TileCells = 0;
|
int32 TileCells = 0;
|
||||||
int32 StrateIndex = INT32_MIN;
|
int32 StrateIndex = INT32_MIN;
|
||||||
@@ -2314,17 +2298,6 @@ namespace
|
|||||||
|
|
||||||
static thread_local FTunnelCoreCacheState GTunnelCoreCache;
|
static thread_local FTunnelCoreCacheState GTunnelCoreCache;
|
||||||
|
|
||||||
static void BuildTunnelSupportColumnsForChunk(
|
|
||||||
FChunkSDFCache& Cache,
|
|
||||||
const FIntVector& ChunkCoord,
|
|
||||||
bool bUseSpatialIndex);
|
|
||||||
|
|
||||||
static const FTunnelSupportFloorColumn* FindTunnelSupportColumn(
|
|
||||||
const FChunkSDFCache& Cache,
|
|
||||||
int32 X,
|
|
||||||
int32 Y,
|
|
||||||
const FTunnelSupportFloorColumn& EmptyColumn);
|
|
||||||
|
|
||||||
// A mesher tile samples an expanded grid (-1..Cells+1), so one worker visits several exact
|
// A mesher tile samples an expanded grid (-1..Cells+1), so one worker visits several exact
|
||||||
// chunk coordinates even while it is generating one tile. The old CP_* cache retained only
|
// chunk coordinates even while it is generating one tile. The old CP_* cache retained only
|
||||||
// the last coordinate and consequently rebuilt the whole TunnelNetwork stack whenever the
|
// the last coordinate and consequently rebuilt the whole TunnelNetwork stack whenever the
|
||||||
@@ -2507,15 +2480,6 @@ namespace
|
|||||||
&& OutState.TileCells == RequestedTileCells));
|
&& OutState.TileCells == RequestedTileCells));
|
||||||
if (bSameGraphWindow)
|
if (bSameGraphWindow)
|
||||||
{
|
{
|
||||||
// The graph is shared by the whole tile, but the sparse support table is deliberately
|
|
||||||
// kept for the currently queried exact chunk. Rebuild only that small table when the
|
|
||||||
// density cursor crosses a chunk; the room/tunnel graph itself is not rebuilt.
|
|
||||||
if (OutState.SupportChunk != ChunkCoord)
|
|
||||||
{
|
|
||||||
BuildTunnelSupportColumnsForChunk(
|
|
||||||
OutState.Cache, ChunkCoord, bUseSpatialIndex);
|
|
||||||
OutState.SupportChunk = ChunkCoord;
|
|
||||||
}
|
|
||||||
OutState.Chunk = ChunkCoord;
|
OutState.Chunk = ChunkCoord;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2552,8 +2516,6 @@ namespace
|
|||||||
StrateIndex, nullptr,
|
StrateIndex, nullptr,
|
||||||
ERoomGraphBuildSite::GeneratorTunnelCore);
|
ERoomGraphBuildSite::GeneratorTunnelCore);
|
||||||
UpdateTunnelCoreTileReach(OutState.Cache, bUseTileCacheWindow);
|
UpdateTunnelCoreTileReach(OutState.Cache, bUseTileCacheWindow);
|
||||||
BuildTunnelSupportColumnsForChunk(
|
|
||||||
OutState.Cache, ChunkCoord, bUseSpatialIndex);
|
|
||||||
OutState.OwnerId = OwnerId;
|
OutState.OwnerId = OwnerId;
|
||||||
OutState.ManagerLifetimeId = ManagerLifetimeId;
|
OutState.ManagerLifetimeId = ManagerLifetimeId;
|
||||||
OutState.Chunk = ChunkCoord;
|
OutState.Chunk = ChunkCoord;
|
||||||
@@ -2561,7 +2523,6 @@ namespace
|
|||||||
? RequestedTileOrigin : FIntVector::ZeroValue;
|
? RequestedTileOrigin : FIntVector::ZeroValue;
|
||||||
OutState.TileStep = bUseTileCacheWindow ? RequestedTileStep : 0;
|
OutState.TileStep = bUseTileCacheWindow ? RequestedTileStep : 0;
|
||||||
OutState.TileCells = bUseTileCacheWindow ? RequestedTileCells : 0;
|
OutState.TileCells = bUseTileCacheWindow ? RequestedTileCells : 0;
|
||||||
OutState.SupportChunk = ChunkCoord;
|
|
||||||
OutState.StrateIndex = StrateIndex;
|
OutState.StrateIndex = StrateIndex;
|
||||||
OutState.WorldSeed = WorldSeed;
|
OutState.WorldSeed = WorldSeed;
|
||||||
OutState.ParamsFingerprint = ParamsFingerprint;
|
OutState.ParamsFingerprint = ParamsFingerprint;
|
||||||
@@ -2570,69 +2531,6 @@ namespace
|
|||||||
OutState.bValid = true;
|
OutState.bValid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void BuildTunnelSupportColumnsForChunk(
|
|
||||||
FChunkSDFCache& Cache,
|
|
||||||
const FIntVector& ChunkCoord,
|
|
||||||
bool bUseSpatialIndex)
|
|
||||||
{
|
|
||||||
const int32 MinX = ChunkCoord.X * CHUNK_SIZE - 1;
|
|
||||||
const int32 MinY = ChunkCoord.Y * CHUNK_SIZE - 1;
|
|
||||||
const int32 Cells = CHUNK_SIZE + 2;
|
|
||||||
Cache.SupportColumnMinX = MinX;
|
|
||||||
Cache.SupportColumnMinY = MinY;
|
|
||||||
Cache.SupportColumnCellsX = Cells;
|
|
||||||
Cache.SupportColumnCellsY = Cells;
|
|
||||||
Cache.SupportColumnEntries.Init(INDEX_NONE, Cells * Cells);
|
|
||||||
Cache.SupportColumns.Reset();
|
|
||||||
|
|
||||||
// Empty columns are represented only by INDEX_NONE in the slot map, so
|
|
||||||
// each stored column retains the inline bands for actual graph tubes.
|
|
||||||
for (int32 Y = 0; Y < Cells; ++Y)
|
|
||||||
{
|
|
||||||
for (int32 X = 0; X < Cells; ++X)
|
|
||||||
{
|
|
||||||
FTunnelSupportFloorColumn Column;
|
|
||||||
VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
|
|
||||||
static_cast<float>(MinX + X),
|
|
||||||
static_cast<float>(MinY + Y),
|
|
||||||
Cache,
|
|
||||||
Column,
|
|
||||||
bUseSpatialIndex);
|
|
||||||
if (Column.Intervals.Num() == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int32 EntryIndex = Cache.SupportColumns.Add(MoveTemp(Column));
|
|
||||||
Cache.SupportColumnEntries[Y * Cells + X] = EntryIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const FTunnelSupportFloorColumn* FindTunnelSupportColumn(
|
|
||||||
const FChunkSDFCache& Cache,
|
|
||||||
int32 X,
|
|
||||||
int32 Y,
|
|
||||||
const FTunnelSupportFloorColumn& EmptyColumn)
|
|
||||||
{
|
|
||||||
if (Cache.SupportColumnCellsX <= 0
|
|
||||||
|| X < Cache.SupportColumnMinX
|
|
||||||
|| X >= Cache.SupportColumnMinX + Cache.SupportColumnCellsX
|
|
||||||
|| Y < Cache.SupportColumnMinY
|
|
||||||
|| Y >= Cache.SupportColumnMinY + Cache.SupportColumnCellsY)
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int32 Slot =
|
|
||||||
(Y - Cache.SupportColumnMinY) * Cache.SupportColumnCellsX
|
|
||||||
+ (X - Cache.SupportColumnMinX);
|
|
||||||
const int32 EntryIndex = Cache.SupportColumnEntries[Slot];
|
|
||||||
return EntryIndex == INDEX_NONE
|
|
||||||
? &EmptyColumn
|
|
||||||
: &Cache.SupportColumns[EntryIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FVoxelStackParamRefs
|
struct FVoxelStackParamRefs
|
||||||
{
|
{
|
||||||
const FSlabGenerationParams* Slab = nullptr;
|
const FSlabGenerationParams* Slab = nullptr;
|
||||||
@@ -2842,7 +2740,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
VoxelDensityProfile::EBucket::DensityPrologue);
|
VoxelDensityProfile::EBucket::DensityPrologue);
|
||||||
// ── STRATE SYSTEM ──
|
// ── STRATE SYSTEM ──
|
||||||
// Query per-chunk params from the manager so each strate has different caves.
|
// Query per-chunk params from the manager so each strate has different caves.
|
||||||
float Result;
|
float Result = 0.0f;
|
||||||
|
|
||||||
// A vertical tree connector may need to stay open through the shared disturbance and
|
// A vertical tree connector may need to stay open through the shared disturbance and
|
||||||
// structural-post tail below. This is one bit for this density call, not cached voxel data.
|
// structural-post tail below. This is one bit for this density call, not cached voxel data.
|
||||||
@@ -3391,6 +3289,9 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
float BlockTunnelCoreSDF = FLT_MAX;
|
float BlockTunnelCoreSDF = FLT_MAX;
|
||||||
bool bBlockTunnelCoreSupportFloor = false;
|
bool bBlockTunnelCoreSupportFloor = false;
|
||||||
bool bBlockTunnelCoreRoomFloor = false;
|
bool bBlockTunnelCoreRoomFloor = false;
|
||||||
|
bool bBlockHasTunnelCoreSweptFloor = false;
|
||||||
|
float BlockTunnelCoreSweptFloorZ = -FLT_MAX;
|
||||||
|
float BlockTunnelCoreSweptFloorRadius = 0.0f;
|
||||||
bool bUsedFusedEvaluator = false;
|
bool bUsedFusedEvaluator = false;
|
||||||
FTunnelCoreWorldEvaluation FusedTunnelCore;
|
FTunnelCoreWorldEvaluation FusedTunnelCore;
|
||||||
|
|
||||||
@@ -3457,6 +3358,12 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
BlockTunnelCoreSDF = BlockCoreSample.TunnelCoreWorldSDF;
|
BlockTunnelCoreSDF = BlockCoreSample.TunnelCoreWorldSDF;
|
||||||
bBlockTunnelCoreSupportFloor = BlockCoreSample.bTunnelCoreSupportFloor;
|
bBlockTunnelCoreSupportFloor = BlockCoreSample.bTunnelCoreSupportFloor;
|
||||||
bBlockTunnelCoreRoomFloor = BlockCoreSample.bTunnelCoreRoomFloor;
|
bBlockTunnelCoreRoomFloor = BlockCoreSample.bTunnelCoreRoomFloor;
|
||||||
|
bBlockHasTunnelCoreSweptFloor =
|
||||||
|
BlockCoreSample.bHasTunnelCoreSweptFloor;
|
||||||
|
BlockTunnelCoreSweptFloorZ =
|
||||||
|
BlockCoreSample.TunnelCoreSweptFloorZ;
|
||||||
|
BlockTunnelCoreSweptFloorRadius =
|
||||||
|
BlockCoreSample.TunnelCoreSweptFloorRadius;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!bUsedFusedEvaluator)
|
if (!bUsedFusedEvaluator)
|
||||||
@@ -3550,6 +3457,11 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
PreDisturbanceTunnelCore.SDF = BlockTunnelCoreSDF;
|
PreDisturbanceTunnelCore.SDF = BlockTunnelCoreSDF;
|
||||||
PreDisturbanceTunnelCore.bSupportFloor = bBlockTunnelCoreSupportFloor;
|
PreDisturbanceTunnelCore.bSupportFloor = bBlockTunnelCoreSupportFloor;
|
||||||
PreDisturbanceTunnelCore.bRoomFloor = bBlockTunnelCoreRoomFloor;
|
PreDisturbanceTunnelCore.bRoomFloor = bBlockTunnelCoreRoomFloor;
|
||||||
|
PreDisturbanceTunnelCore.bHasSweptFloor =
|
||||||
|
bBlockHasTunnelCoreSweptFloor;
|
||||||
|
PreDisturbanceTunnelCore.SweptFloorZ = BlockTunnelCoreSweptFloorZ;
|
||||||
|
PreDisturbanceTunnelCore.SweptFloorRadius =
|
||||||
|
BlockTunnelCoreSweptFloorRadius;
|
||||||
bHavePreDisturbanceTunnelCore = true;
|
bHavePreDisturbanceTunnelCore = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -3561,30 +3473,14 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
if (!VoxelDensityAblation::IsTunnelCoreOff()
|
if (!VoxelDensityAblation::IsTunnelCoreOff()
|
||||||
&& !bHavePreDisturbanceTunnelCore && ActiveTunnelCoreCache->bValid)
|
&& !bHavePreDisturbanceTunnelCore && ActiveTunnelCoreCache->bValid)
|
||||||
{
|
{
|
||||||
const FTunnelSupportFloorColumn* SupportColumn = nullptr;
|
|
||||||
FTunnelSupportFloorColumn EmptySupportColumn;
|
|
||||||
const bool bIntegerXY =
|
|
||||||
WorldX == FMath::FloorToFloat(WorldX)
|
|
||||||
&& WorldY == FMath::FloorToFloat(WorldY);
|
|
||||||
if (bIntegerXY)
|
|
||||||
{
|
|
||||||
const int32 IX = FMath::FloorToInt(WorldX);
|
|
||||||
const int32 IY = FMath::FloorToInt(WorldY);
|
|
||||||
SupportColumn = FindTunnelSupportColumn(
|
|
||||||
ActiveTunnelCoreCache->Cache, IX, IY, EmptySupportColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::TunnelCorePosts);
|
VoxelDensityProfile::EBucket::TunnelCorePosts);
|
||||||
PreDisturbanceTunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
PreDisturbanceTunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||||
WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache, SupportColumn,
|
WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache, nullptr,
|
||||||
VoxelGenLOD::ShouldUseSpatialIndex(
|
VoxelGenLOD::ShouldUseSpatialIndex(
|
||||||
CP_UseOpStack ? bUsedFusedEvaluator : true));
|
CP_UseOpStack ? bUsedFusedEvaluator : true));
|
||||||
bHavePreDisturbanceTunnelCore = true;
|
bHavePreDisturbanceTunnelCore = true;
|
||||||
}
|
}
|
||||||
const bool bProtectAuthoredTunnelFloor = bHavePreDisturbanceTunnelCore
|
|
||||||
&& (PreDisturbanceTunnelCore.bSupportFloor
|
|
||||||
|| PreDisturbanceTunnelCore.bRoomFloor);
|
|
||||||
|
|
||||||
// Disturbance layer (the "wow" post-process) — cached params, MC convention. The
|
// Disturbance layer (the "wow" post-process) — cached params, MC convention. The
|
||||||
// vertical shaft tree opens a walkable route before this shared pass, so a bridge/ridge
|
// vertical shaft tree opens a walkable route before this shared pass, so a bridge/ridge
|
||||||
@@ -3594,7 +3490,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
const bool bProtectVerticalShaftAir =
|
const bool bProtectVerticalShaftAir =
|
||||||
VoxelPassageGeometry::VerticalShaftConnectorAirMarker();
|
VoxelPassageGeometry::VerticalShaftConnectorAirMarker();
|
||||||
ApplyDisturbances(Result, WorldX, WorldY, WorldZ, CP_Dist, (uint32)Seed,
|
ApplyDisturbances(Result, WorldX, WorldY, WorldZ, CP_Dist, (uint32)Seed,
|
||||||
bProtectVerticalShaftAir, bProtectAuthoredTunnelFloor);
|
bProtectVerticalShaftAir);
|
||||||
DensityDisturbancesTimer.End();
|
DensityDisturbancesTimer.End();
|
||||||
|
|
||||||
// A disturbance is allowed to add visual detail, but it must not refill the landing's
|
// A disturbance is allowed to add visual detail, but it must not refill the landing's
|
||||||
@@ -3670,6 +3566,27 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
auto ApplyTunnelCoreShape = [&](const FTunnelCoreWorldEvaluation& Core)
|
||||||
|
{
|
||||||
|
if (!VoxelMath::IsFinite(Core.SDF))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bool bOwnsSweptBottom =
|
||||||
|
Core.bHasSweptFloor
|
||||||
|
&& VoxelMath::IsFinite(Core.SweptFloorZ)
|
||||||
|
&& VoxelMath::IsFinite(Core.SweptFloorRadius)
|
||||||
|
&& WorldZ <= Core.SweptFloorZ
|
||||||
|
+ VoxelPassageGeometry::WalkableTunnelFloorAirClearanceVoxels;
|
||||||
|
if (bOwnsSweptBottom)
|
||||||
|
{
|
||||||
|
Result = FMath::Min(Result, -Core.SDF);
|
||||||
|
}
|
||||||
|
else if (Core.SDF < 0.0f)
|
||||||
|
{
|
||||||
|
Result = FMath::Max(Result, -Core.SDF);
|
||||||
|
}
|
||||||
|
};
|
||||||
// Origin rooms are structural too. Reassert their air before either support writer so a
|
// Origin rooms are structural too. Reassert their air before either support writer so a
|
||||||
// bridge/ridge cannot plug a landing, while the two floor writers remain last.
|
// bridge/ridge cannot plug a landing, while the two floor writers remain last.
|
||||||
{
|
{
|
||||||
@@ -3693,7 +3610,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
VoxelDensityProfile::EBucket::PassageStructuralPosts);
|
VoxelDensityProfile::EBucket::PassageStructuralPosts);
|
||||||
StrateManager->ApplyPassageStructuralPostsMC(
|
StrateManager->ApplyPassageStructuralPostsMC(
|
||||||
Result, WorldX, WorldY, WorldZ, LandingBaseDensity,
|
Result, WorldX, WorldY, WorldZ, LandingBaseDensity,
|
||||||
CP_Dist.BoundarySealThickness, bProtectAuthoredTunnelFloor);
|
CP_Dist.BoundarySealThickness);
|
||||||
}
|
}
|
||||||
// Disturbance features are authored as a generic MC-space post and may add a ridge or
|
// Disturbance features are authored as a generic MC-space post and may add a ridge or
|
||||||
// bridge over a graph tunnel. Reassert the native cached tunnel core here, after every
|
// bridge over a graph tunnel. Reassert the native cached tunnel core here, after every
|
||||||
@@ -3705,8 +3622,10 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
const bool bTunnelCoreBypass = VoxelGenLOD::IsTilePostReachBypassActive();
|
const bool bTunnelCoreBypass = VoxelGenLOD::IsTilePostReachBypassActive();
|
||||||
auto ApplyGraphLandingFloors = [&]()
|
auto ApplyGraphLandingFloors = [&]()
|
||||||
{
|
{
|
||||||
// These are passage/origin landing posts, not tunnel-core writes. Keep them alive when
|
// These are passage/origin landing posts, not tunnel-core writes. Their air and
|
||||||
// the core bit is clear because their own per-tile reach bits may still be set.
|
// structural post already ran before disturbances; the tunnel shape below reopens
|
||||||
|
// its own continuous interior after the disturbance pass. Keep only the floor
|
||||||
|
// reassertions here, in the existing order.
|
||||||
{
|
{
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::PassageLandingFloor);
|
VoxelDensityProfile::EBucket::PassageLandingFloor);
|
||||||
@@ -3733,7 +3652,9 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
{
|
{
|
||||||
FTunnelCoreWorldEvaluation TunnelCore;
|
FTunnelCoreWorldEvaluation TunnelCore;
|
||||||
bool bHaveTunnelCore = bHavePreDisturbanceTunnelCore;
|
bool bHaveTunnelCore = bHavePreDisturbanceTunnelCore;
|
||||||
if (!bTunnelCoreReachable && !bTunnelCoreBypass)
|
if (VoxelGenLOD::bTileCoreReachProofEnabled
|
||||||
|
&& VoxelGenLOD::bTileReachDiagnosticTile
|
||||||
|
&& !bTunnelCoreReachable && !bTunnelCoreBypass)
|
||||||
{
|
{
|
||||||
// The fused/source hand-off is also empty on the skipped path. Do not let the
|
// The fused/source hand-off is also empty on the skipped path. Do not let the
|
||||||
// default object turn this into a second tail evaluation.
|
// default object turn this into a second tail evaluation.
|
||||||
@@ -3746,83 +3667,26 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
if (!bHaveTunnelCore && ActiveTunnelCoreCache->bValid
|
if (!bHaveTunnelCore && ActiveTunnelCoreCache->bValid
|
||||||
&& (bTunnelCoreReachable || bTunnelCoreBypass))
|
&& (bTunnelCoreReachable || bTunnelCoreBypass))
|
||||||
{
|
{
|
||||||
const FTunnelSupportFloorColumn* SupportColumn = nullptr;
|
|
||||||
FTunnelSupportFloorColumn EmptySupportColumn;
|
|
||||||
const bool bIntegerXY =
|
|
||||||
WorldX == FMath::FloorToFloat(WorldX)
|
|
||||||
&& WorldY == FMath::FloorToFloat(WorldY);
|
|
||||||
if (bIntegerXY)
|
|
||||||
{
|
|
||||||
const int32 IX = FMath::FloorToInt(WorldX);
|
|
||||||
const int32 IY = FMath::FloorToInt(WorldY);
|
|
||||||
SupportColumn = FindTunnelSupportColumn(
|
|
||||||
ActiveTunnelCoreCache->Cache, IX, IY, EmptySupportColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::TunnelCorePosts);
|
VoxelDensityProfile::EBucket::TunnelCorePosts);
|
||||||
TunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
TunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||||
WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache,
|
WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache,
|
||||||
SupportColumn,
|
nullptr,
|
||||||
VoxelGenLOD::ShouldUseSpatialIndex(
|
VoxelGenLOD::ShouldUseSpatialIndex(
|
||||||
CP_UseOpStack ? bUsedFusedEvaluator : true));
|
CP_UseOpStack ? bUsedFusedEvaluator : true));
|
||||||
bHaveTunnelCore = true;
|
bHaveTunnelCore = true;
|
||||||
}
|
}
|
||||||
if (bHaveTunnelCore)
|
if (bHaveTunnelCore)
|
||||||
{
|
{
|
||||||
const bool bTunnelSupportFloor = TunnelCore.bSupportFloor;
|
// Reassert the actual swept-core field after generic writers. The old tail used a
|
||||||
// The graph floor is now composed here, after disturbances and passage writers.
|
// fixed positive air value once the core was deeper than an inset; that hard
|
||||||
// The source has already baked the floor profile and published the exact same
|
// switch discarded the SDF magnitude and made marching-cubes intersections snap
|
||||||
// core result; this final ownership step only changes an actually-air sample.
|
// to the sample lattice. The source now contains the same continuous world-chain
|
||||||
// Previously the operator stack raised every authored floor sample to a strong
|
// tunnel field. Union its interior air back over disturbances, and use the same
|
||||||
// internal density before those writers ran, which made the protection counter
|
// signed field only on the bottom side identified by the swept shape itself. This
|
||||||
// fire for solid samples as well as genuine breaches.
|
// restores the tunnel's carved floor when the surrounding room is already air,
|
||||||
if (TunnelCore.bRoomFloor)
|
// without adding an authored slab or turning the whole room into tunnel geometry.
|
||||||
{
|
ApplyTunnelCoreShape(TunnelCore);
|
||||||
const float StructuralSolidDensity =
|
|
||||||
-FMath::Max(LandingBaseDensity * 2.0f, 1.0f);
|
|
||||||
if (VoxelDensityProfile::AreCountersEnabled())
|
|
||||||
{
|
|
||||||
if (Result > 0.0f)
|
|
||||||
{
|
|
||||||
VoxelDensityProfile::AddCounter(
|
|
||||||
VoxelDensityProfile::ECounter::TunnelRoomFloorBackstopFires);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// A tunnel that penetrates a room cannot reassert its own floor through the
|
|
||||||
// room's floor. The morphology query marks only the finite room support band;
|
|
||||||
// preserve it after every generic post-disturbance writer.
|
|
||||||
Result = FMath::Min(
|
|
||||||
Result,
|
|
||||||
StructuralSolidDensity);
|
|
||||||
}
|
|
||||||
// The swept capsule owns the relief profile. This finite support band is now the
|
|
||||||
// final composition of that authored shape, after every writer, rather than a
|
|
||||||
// pre-disturbance clamp in FVoxelOpStack::EvalSample.
|
|
||||||
if (bTunnelSupportFloor)
|
|
||||||
{
|
|
||||||
const float StructuralSolidDensity =
|
|
||||||
-FMath::Max(LandingBaseDensity * 2.0f, 1.0f);
|
|
||||||
if (VoxelDensityProfile::AreCountersEnabled())
|
|
||||||
{
|
|
||||||
if (Result > 0.0f)
|
|
||||||
{
|
|
||||||
VoxelDensityProfile::AddCounter(
|
|
||||||
VoxelDensityProfile::ECounter::TunnelSupportFloorBackstopFires);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Result = FMath::Min(
|
|
||||||
Result,
|
|
||||||
StructuralSolidDensity);
|
|
||||||
}
|
|
||||||
const float CoreSDF = TunnelCore.SDF;
|
|
||||||
if (!bTunnelSupportFloor
|
|
||||||
&& CoreSDF < -VoxelPassageGeometry::CaveTunnelAirCoreInsetVoxels)
|
|
||||||
{
|
|
||||||
Result = FMath::Max(
|
|
||||||
Result,
|
|
||||||
FMath::Max(LandingBaseDensity * 2.0f, 1.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
if (bHaveTunnelCore || (!bTunnelCoreReachable && !bTunnelCoreBypass))
|
if (bHaveTunnelCore || (!bTunnelCoreReachable && !bTunnelCoreBypass))
|
||||||
@@ -4292,11 +4156,13 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
// Also writes NearestRoomIdx — the room with minimum SDF contribution
|
// Also writes NearestRoomIdx — the room with minimum SDF contribution
|
||||||
// at this voxel's position. Used by terrain ops below to pick per-room params.
|
// at this voxel's position. Used by terrain ops below to pick per-room params.
|
||||||
NearestRoomIdx = -1;
|
NearestRoomIdx = -1;
|
||||||
|
const FVector WorldTunnelPosition(WorldX, WorldY, WorldZ);
|
||||||
CaveSDF = VoxelCaveMorphology::EvaluateSDFCached(
|
CaveSDF = VoxelCaveMorphology::EvaluateSDFCached(
|
||||||
WarpedX, WarpedY, WarpedZ,
|
WarpedX, WarpedY, WarpedZ,
|
||||||
SDFCache, Params.SDFBlendRadius,
|
SDFCache, Params.SDFBlendRadius,
|
||||||
&NearestRoomIdx,
|
&NearestRoomIdx,
|
||||||
bUseSpatialIndex
|
bUseSpatialIndex,
|
||||||
|
&WorldTunnelPosition
|
||||||
);
|
);
|
||||||
|
|
||||||
// ── PIT & CHIMNEY SDF INTEGRATION ──
|
// ── PIT & CHIMNEY SDF INTEGRATION ──
|
||||||
@@ -4640,12 +4506,14 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
//
|
//
|
||||||
// We use the cached SDF so this costs two extra SDF evaluations per voxel,
|
// We use the cached SDF so this costs two extra SDF evaluations per voxel,
|
||||||
// only when near a surface — the common case is cheap (DistFromSurface > TerraceRange).
|
// only when near a surface — the common case is cheap (DistFromSurface > TerraceRange).
|
||||||
|
const FVector WorldTunnelPositionZp1(WorldX, WorldY, WorldZ + 1.0f);
|
||||||
|
const FVector WorldTunnelPositionZm1(WorldX, WorldY, WorldZ - 1.0f);
|
||||||
float SDF_Zp1 = VoxelCaveMorphology::EvaluateSDFCached(
|
float SDF_Zp1 = VoxelCaveMorphology::EvaluateSDFCached(
|
||||||
WorldX, WorldY, WorldZ + 1.0f, SDFCache, Params.SDFBlendRadius,
|
WorldX, WorldY, WorldZ + 1.0f, SDFCache, Params.SDFBlendRadius,
|
||||||
nullptr, bUseSpatialIndex);
|
nullptr, bUseSpatialIndex, &WorldTunnelPositionZp1);
|
||||||
float SDF_Zm1 = VoxelCaveMorphology::EvaluateSDFCached(
|
float SDF_Zm1 = VoxelCaveMorphology::EvaluateSDFCached(
|
||||||
WorldX, WorldY, WorldZ - 1.0f, SDFCache, Params.SDFBlendRadius,
|
WorldX, WorldY, WorldZ - 1.0f, SDFCache, Params.SDFBlendRadius,
|
||||||
nullptr, bUseSpatialIndex);
|
nullptr, bUseSpatialIndex, &WorldTunnelPositionZm1);
|
||||||
float GZ = (SDF_Zp1 - SDF_Zm1) * 0.5f;
|
float GZ = (SDF_Zp1 - SDF_Zm1) * 0.5f;
|
||||||
// Normalized vertical component: 1 = perfectly horizontal surface (floor/ceiling)
|
// Normalized vertical component: 1 = perfectly horizontal surface (floor/ceiling)
|
||||||
// 0 = perfectly vertical surface (wall)
|
// 0 = perfectly vertical surface (wall)
|
||||||
@@ -5208,41 +5076,19 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
|
|
||||||
if (bApplyLegacyStructuralPosts)
|
if (bApplyLegacyStructuralPosts)
|
||||||
{
|
{
|
||||||
// Terrain operations and passage support floors are allowed to write solid density, but
|
// Direct callers retain the legacy structural tail. The tunnel core itself is still
|
||||||
// the graph's own tunnel floor/core are structural route geometry. Direct callers of
|
// composed here as a continuous field; it is not converted into a fixed-density slab.
|
||||||
// GetDensityWithParams retain this historical tail; GetDensityAt skips it and applies the
|
|
||||||
// same predicates once in its common MC-space post section below both generation paths.
|
|
||||||
const bool bTunnelCoreEnabled = !VoxelDensityAblation::IsTunnelCoreOff();
|
const bool bTunnelCoreEnabled = !VoxelDensityAblation::IsTunnelCoreOff();
|
||||||
const bool bTunnelSupportFloor = bTunnelCoreEnabled
|
|
||||||
&& VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
|
|
||||||
WorldX, WorldY, WorldZ, SDFCache, bUseSpatialIndex);
|
|
||||||
// Keep the direct legacy entry point capability-safe for callers outside the canonical
|
|
||||||
// operator-stack path; its support band follows the same relieved swept floor.
|
|
||||||
if (bTunnelSupportFloor)
|
|
||||||
{
|
|
||||||
const float StructuralSolidDensity =
|
|
||||||
FMath::Max(Params.BaseDensity * 2.0f, 1.0f);
|
|
||||||
if (VoxelDensityProfile::AreCountersEnabled())
|
|
||||||
{
|
|
||||||
if (Density < 0.0f)
|
|
||||||
{
|
|
||||||
VoxelDensityProfile::AddCounter(
|
|
||||||
VoxelDensityProfile::ECounter::TunnelSupportFloorBackstopFires);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Density = FMath::Max(Density, StructuralSolidDensity);
|
|
||||||
}
|
|
||||||
const float TunnelCoreSDF = bTunnelCoreEnabled
|
const float TunnelCoreSDF = bTunnelCoreEnabled
|
||||||
&& (Params.RoomDensity > 0.0f && Params.RoomSpacing > 0.0f)
|
&& (Params.RoomDensity > 0.0f && Params.RoomSpacing > 0.0f)
|
||||||
? VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(
|
? VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(
|
||||||
WorldX, WorldY, WorldZ, SDFCache, bUseSpatialIndex)
|
WorldX, WorldY, WorldZ, SDFCache, bUseSpatialIndex)
|
||||||
: FLT_MAX;
|
: FLT_MAX;
|
||||||
if (!bTunnelSupportFloor
|
if (VoxelMath::IsFinite(TunnelCoreSDF) && TunnelCoreSDF < 0.0f)
|
||||||
&& TunnelCoreSDF < -VoxelPassageGeometry::CaveTunnelAirCoreInsetVoxels)
|
|
||||||
{
|
{
|
||||||
Density = FMath::Min(
|
Density = FMath::Min(
|
||||||
Density,
|
Density,
|
||||||
-FMath::Max(Params.BaseDensity * 2.0f, 1.0f));
|
TunnelCoreSDF);
|
||||||
}
|
}
|
||||||
ApplyOriginLandingFloor(Density, WorldX, WorldY, WorldZ,
|
ApplyOriginLandingFloor(Density, WorldX, WorldY, WorldZ,
|
||||||
Params.StrateTopWorldZ, Params.StrateBottomWorldZ,
|
Params.StrateTopWorldZ, Params.StrateBottomWorldZ,
|
||||||
|
|||||||
@@ -1371,13 +1371,6 @@ namespace
|
|||||||
Params.bTunnelsFlowTowardOrigin = CategoryRng.FRand() < 0.5f;
|
Params.bTunnelsFlowTowardOrigin = CategoryRng.FRand() < 0.5f;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (FCString::Strcmp(FieldName, TEXT("bTunnelFloorEnabled")) == 0)
|
|
||||||
{
|
|
||||||
// The floor is a capability feature, not a corpus-free variation axis. Keep it on
|
|
||||||
// for every generated candidate so a roll cannot manufacture an invalid bare tube.
|
|
||||||
Params.bTunnelFloorEnabled = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (FCString::Strcmp(FieldName, TEXT("bTunnelFloorTerracingEnabled")) == 0)
|
if (FCString::Strcmp(FieldName, TEXT("bTunnelFloorTerracingEnabled")) == 0)
|
||||||
{
|
{
|
||||||
Params.bTunnelFloorTerracingEnabled = CategoryRng.FRand() < 0.75f;
|
Params.bTunnelFloorTerracingEnabled = CategoryRng.FRand() < 0.75f;
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ void UVoxelStrateDefinition::PostLoad()
|
|||||||
{
|
{
|
||||||
Super::PostLoad();
|
Super::PostLoad();
|
||||||
|
|
||||||
|
// The field remains reflected solely so old packages deserialize without shifting the
|
||||||
|
// following members. Tunnel geometry owns the floor now, so every serialized legacy value
|
||||||
|
// is normalized to the ignored compatibility default before any runtime copy is made.
|
||||||
|
GenerationParams.bTunnelFloorEnabled = true;
|
||||||
|
|
||||||
if (GetLinkerCustomVersion(GVoxelForgeStrateSerializationVersion)
|
if (GetLinkerCustomVersion(GVoxelForgeStrateSerializationVersion)
|
||||||
>= GVoxelForgeStrateGentleSlopeDegreesVersion)
|
>= GVoxelForgeStrateGentleSlopeDegreesVersion)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2967,8 +2967,7 @@ static bool VF_IsPassageRoomFloor(
|
|||||||
|
|
||||||
void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
|
void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
|
||||||
float& Density, float WorldX, float WorldY, float WorldZ,
|
float& Density, float WorldX, float WorldY, float WorldZ,
|
||||||
float BaseDensity, float SealThickness,
|
float BaseDensity, float SealThickness) const
|
||||||
bool bProtectAuthoredTunnelFloor) const
|
|
||||||
{
|
{
|
||||||
const bool bDisableStructuralPosts = VoxelDensityAblation::IsPassageStructuralPostsOff();
|
const bool bDisableStructuralPosts = VoxelDensityAblation::IsPassageStructuralPostsOff();
|
||||||
const bool bDisableLandingPosts = VoxelDensityAblation::IsLandingPostsOff();
|
const bool bDisableLandingPosts = VoxelDensityAblation::IsLandingPostsOff();
|
||||||
@@ -2989,7 +2988,7 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
|
|||||||
VoxelGenLOD::bTilePostReachBypass = true;
|
VoxelGenLOD::bTilePostReachBypass = true;
|
||||||
ApplyPassageStructuralPostsMC(
|
ApplyPassageStructuralPostsMC(
|
||||||
CanonicalDensity, WorldX, WorldY, WorldZ,
|
CanonicalDensity, WorldX, WorldY, WorldZ,
|
||||||
BaseDensity, SealThickness, bProtectAuthoredTunnelFloor);
|
BaseDensity, SealThickness);
|
||||||
VoxelGenLOD::bTilePostReachBypass = PreviousBypass;
|
VoxelGenLOD::bTilePostReachBypass = PreviousBypass;
|
||||||
}
|
}
|
||||||
if (!bLandingReachable && !bStructuralReachable)
|
if (!bLandingReachable && !bStructuralReachable)
|
||||||
@@ -3076,11 +3075,9 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A graph tunnel floor is authored by the room-graph source with its complete clearance
|
// The graph tunnel no longer exposes a separate support slab. Inter-strate landing carving
|
||||||
// budget. A neighbouring inter-strate landing must not reopen that finite support band; its
|
// remains independent and is still composed here when its own reach proof allows it.
|
||||||
// carve remains valid everywhere else. Legacy callers leave this false and retain the old
|
if (!bDisableLandingPosts
|
||||||
// passage-owned behavior.
|
|
||||||
if (!bDisableLandingPosts && !bProtectAuthoredTunnelFloor
|
|
||||||
&& !bAnyLandingFloor && !bLegacyTunnelSupportFloor && MinLandingSDF < FLT_MAX)
|
&& !bAnyLandingFloor && !bLegacyTunnelSupportFloor && MinLandingSDF < FLT_MAX)
|
||||||
{
|
{
|
||||||
float InternalDensity = -Density;
|
float InternalDensity = -Density;
|
||||||
@@ -3094,7 +3091,7 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
|
|||||||
Density = FMath::Min(Density, -BaseDensity);
|
Density = FMath::Min(Density, -BaseDensity);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bWalkableAir && !bProtectAuthoredTunnelFloor)
|
if (bWalkableAir)
|
||||||
{
|
{
|
||||||
Density = FMath::Max(
|
Density = FMath::Max(
|
||||||
Density, BaseDensity * 2.0f + SealThickness + 4.0f);
|
Density, BaseDensity * 2.0f + SealThickness + 4.0f);
|
||||||
|
|||||||
@@ -712,7 +712,6 @@ struct FCachedTunnel
|
|||||||
float SDFMouthFloorZB = -FLT_MAX;
|
float SDFMouthFloorZB = -FLT_MAX;
|
||||||
float WorldMouthFloorZA = -FLT_MAX;
|
float WorldMouthFloorZA = -FLT_MAX;
|
||||||
float WorldMouthFloorZB = -FLT_MAX;
|
float WorldMouthFloorZB = -FLT_MAX;
|
||||||
bool bTunnelFloorEnabled = true;
|
|
||||||
bool bTunnelFloorTerracingEnabled = true;
|
bool bTunnelFloorTerracingEnabled = true;
|
||||||
float TunnelFloorTerraceStepHeight = 1.71f;
|
float TunnelFloorTerraceStepHeight = 1.71f;
|
||||||
float TunnelFloorMaxLedgeHeight = 12.0f;
|
float TunnelFloorMaxLedgeHeight = 12.0f;
|
||||||
@@ -915,17 +914,8 @@ struct FChunkSDFCache
|
|||||||
FChunkSDFSpatialIndex ChimneySpatialIndex;
|
FChunkSDFSpatialIndex ChimneySpatialIndex;
|
||||||
FChunkSDFSpatialIndex ColumnSpatialIndex;
|
FChunkSDFSpatialIndex ColumnSpatialIndex;
|
||||||
|
|
||||||
// Sparse immutable support-column table. SupportColumnEntries is a dense
|
|
||||||
// integer-XY slot map whose values index only non-empty SupportColumns.
|
|
||||||
int32 SupportColumnMinX = 0;
|
|
||||||
int32 SupportColumnMinY = 0;
|
|
||||||
int32 SupportColumnCellsX = 0;
|
|
||||||
int32 SupportColumnCellsY = 0;
|
|
||||||
TArray<int32> SupportColumnEntries;
|
|
||||||
TArray<FTunnelSupportFloorColumn> SupportColumns;
|
|
||||||
|
|
||||||
// Kept with the cache because the world-space structural evaluator has no params argument.
|
// Kept with the cache because the world-space structural evaluator has no params argument.
|
||||||
// A zero value is a valid hard-intersection fallback for legacy/empty caches.
|
// A zero value is a valid fallback for legacy/empty caches.
|
||||||
float SDFBlendRadius = 0.0f;
|
float SDFBlendRadius = 0.0f;
|
||||||
|
|
||||||
// Allocator-backed bytes owned by this cache, including the nested tunnel chains. The returned
|
// Allocator-backed bytes owned by this cache, including the nested tunnel chains. The returned
|
||||||
@@ -943,14 +933,20 @@ struct FChunkSDFCache
|
|||||||
VoxelDensityProfile::FCacheMemoryBreakdown GetAllocatedSizeBreakdown() const;
|
VoxelDensityProfile::FCacheMemoryBreakdown GetAllocatedSizeBreakdown() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Combined world-space tunnel post query; one cache scan supplies both support and air tests. */
|
/** World-space tunnel shape query. Legacy support-floor fields remain source-compatible but are
|
||||||
|
* no longer produced or consumed by the graph-tunnel generation path. */
|
||||||
struct FTunnelCoreWorldEvaluation
|
struct FTunnelCoreWorldEvaluation
|
||||||
{
|
{
|
||||||
float SDF = FLT_MAX;
|
float SDF = FLT_MAX;
|
||||||
|
// Deprecated compatibility output. The separate graph-tunnel support slab was removed.
|
||||||
bool bSupportFloor = false;
|
bool bSupportFloor = false;
|
||||||
// The room floor owns the overlap band. The common post uses this marker to restore
|
// Deprecated compatibility output. Room/landing floors are composed by their own posts.
|
||||||
// the room's support after the raw tunnel-air backstop has run.
|
|
||||||
bool bRoomFloor = false;
|
bool bRoomFloor = false;
|
||||||
|
// Shape-level floor ownership. This is not an authored slab: it identifies the bottom side
|
||||||
|
// of the swept tunnel SDF so a room that is already air cannot erase the tunnel's own floor.
|
||||||
|
bool bHasSweptFloor = false;
|
||||||
|
float SweptFloorZ = -FLT_MAX;
|
||||||
|
float SweptFloorRadius = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
@@ -1040,13 +1036,17 @@ namespace VoxelCaveMorphology
|
|||||||
// SmoothMin. Includes per-room/tunnel distance culling to skip primitives
|
// SmoothMin. Includes per-room/tunnel distance culling to skip primitives
|
||||||
// that are too far to contribute.
|
// that are too far to contribute.
|
||||||
//
|
//
|
||||||
// @param WorldX, WorldY, WorldZ — position in voxel coordinates (may be warped)
|
// @param WorldX, WorldY, WorldZ — room/query position in voxel coordinates (may be warped)
|
||||||
// @param Cache — pre-built cache from BuildChunkCache
|
// @param Cache — pre-built cache from BuildChunkCache
|
||||||
// @param SDFBlendRadius — SmoothMin blend radius (from Params.SDFBlendRadius)
|
// @param SDFBlendRadius — SmoothMin blend radius (from Params.SDFBlendRadius)
|
||||||
// @param OutNearestRoomIdx — optional out: index of the room with minimum SDF
|
// @param OutNearestRoomIdx — optional out: index of the room with minimum SDF
|
||||||
// contribution. -1 if no room passed the cull test.
|
// contribution. -1 if no room passed the cull test.
|
||||||
// Used by the terrain ops system to look up the
|
// Used by the terrain ops system to look up the
|
||||||
// per-room terrain op assigned to this voxel's room.
|
// per-room terrain op assigned to this voxel's room.
|
||||||
|
// @param WorldTunnelPosition — optional unwarped position for the tunnel primitive. When
|
||||||
|
// supplied, rooms remain at the query position but the tunnel
|
||||||
|
// uses its authored world chain. Null preserves the legacy
|
||||||
|
// all-query behavior.
|
||||||
// (Room shape variety is baked into FCachedRoom by BuildChunkCache — no per-voxel roll.)
|
// (Room shape variety is baked into FCachedRoom by BuildChunkCache — no per-voxel roll.)
|
||||||
// @return negative = inside cave, positive = solid rock
|
// @return negative = inside cave, positive = solid rock
|
||||||
VOXELFORGE_API float EvaluateSDFCached(
|
VOXELFORGE_API float EvaluateSDFCached(
|
||||||
@@ -1054,7 +1054,8 @@ namespace VoxelCaveMorphology
|
|||||||
const FChunkSDFCache& Cache,
|
const FChunkSDFCache& Cache,
|
||||||
float SDFBlendRadius,
|
float SDFBlendRadius,
|
||||||
int32* OutNearestRoomIdx = nullptr,
|
int32* OutNearestRoomIdx = nullptr,
|
||||||
bool bUseSpatialIndex = true
|
bool bUseSpatialIndex = true,
|
||||||
|
const FVector* WorldTunnelPosition = nullptr
|
||||||
);
|
);
|
||||||
|
|
||||||
// Evaluate the raw union of cached graph-tunnel capsules. This is intentionally separate from
|
// Evaluate the raw union of cached graph-tunnel capsules. This is intentionally separate from
|
||||||
@@ -1078,6 +1079,7 @@ namespace VoxelCaveMorphology
|
|||||||
VOXELFORGE_API FTunnelCoreWorldEvaluation EvaluateTunnelCoreWorld(
|
VOXELFORGE_API FTunnelCoreWorldEvaluation EvaluateTunnelCoreWorld(
|
||||||
float WorldX, float WorldY, float WorldZ,
|
float WorldX, float WorldY, float WorldZ,
|
||||||
const FChunkSDFCache& Cache,
|
const FChunkSDFCache& Cache,
|
||||||
|
// Deprecated legacy argument; graph-tunnel evaluation ignores the old support column.
|
||||||
const FTunnelSupportFloorColumn* SupportColumn = nullptr,
|
const FTunnelSupportFloorColumn* SupportColumn = nullptr,
|
||||||
bool bUseSpatialIndex = true
|
bool bUseSpatialIndex = true
|
||||||
);
|
);
|
||||||
@@ -1090,18 +1092,16 @@ namespace VoxelCaveMorphology
|
|||||||
float ReachScale = 1.0f
|
float ReachScale = 1.0f
|
||||||
);
|
);
|
||||||
|
|
||||||
// True for the finite support slab beneath a world-space graph tunnel. The native density
|
// Deprecated compatibility query for the removed graph-tunnel support slab. Production
|
||||||
// path and the operator stack use this same predicate before reopening the air core, keeping
|
// generation does not call this predicate; the tunnel's own swept shape owns its floor.
|
||||||
// the rounded capsule's bottom from becoming an unsupported point.
|
|
||||||
VOXELFORGE_API bool IsTunnelSupportFloorWorldPoint(
|
VOXELFORGE_API bool IsTunnelSupportFloorWorldPoint(
|
||||||
float WorldX, float WorldY, float WorldZ,
|
float WorldX, float WorldY, float WorldZ,
|
||||||
const FChunkSDFCache& Cache,
|
const FChunkSDFCache& Cache,
|
||||||
bool bUseSpatialIndex = true
|
bool bUseSpatialIndex = true
|
||||||
);
|
);
|
||||||
|
|
||||||
// Build/query the same support-floor predicate at column granularity. The column stores its
|
// Deprecated compatibility helper for callers that still inspect the old support-band
|
||||||
// finite support interval, so every Z sample in a chunk reuses the same deterministic
|
// diagnostic. It is detached from production density generation.
|
||||||
// projection and never re-runs a natural-floor validator.
|
|
||||||
VOXELFORGE_API void BuildTunnelSupportFloorColumn(
|
VOXELFORGE_API void BuildTunnelSupportFloorColumn(
|
||||||
float WorldX, float WorldY,
|
float WorldX, float WorldY,
|
||||||
const FChunkSDFCache& Cache,
|
const FChunkSDFCache& Cache,
|
||||||
@@ -1114,9 +1114,8 @@ namespace VoxelCaveMorphology
|
|||||||
const FTunnelSupportFloorColumn& Column
|
const FTunnelSupportFloorColumn& Column
|
||||||
);
|
);
|
||||||
|
|
||||||
// Returns the exact projected floor band for one tunnel at this XY column.
|
// Returns the legacy projected band for one tunnel at this XY column. The world evaluator no
|
||||||
// The world evaluator uses FloorZ for its per-tunnel air ownership rule and
|
// longer uses it to write a support slab.
|
||||||
// Min/Max for the support slab itself.
|
|
||||||
VOXELFORGE_API bool GetTunnelSupportFloorColumnBand(
|
VOXELFORGE_API bool GetTunnelSupportFloorColumnBand(
|
||||||
int32 TunnelIndex,
|
int32 TunnelIndex,
|
||||||
const FTunnelSupportFloorColumn& Column,
|
const FTunnelSupportFloorColumn& Column,
|
||||||
|
|||||||
@@ -337,10 +337,8 @@ struct FVoxelOpSample
|
|||||||
// FLT_MAX = "no surface nearby" — the initial state, and the early-out placed sources use.
|
// FLT_MAX = "no surface nearby" — the initial state, and the early-out placed sources use.
|
||||||
float Sdf = FLT_MAX;
|
float Sdf = FLT_MAX;
|
||||||
|
|
||||||
// The room-graph source can author a finite tunnel/room floor band while it is building the
|
// Deprecated structural-floor compatibility fields. Graph tunnels now carry their floor in
|
||||||
// shape. This structural metadata is not a third composable field: it tells the stack that
|
// the swept shape itself; the production stack does not use these fields to write a slab.
|
||||||
// later detail operators must not turn the already-authored support back into air. Carrying it
|
|
||||||
// with the sample keeps the rule true even when a detail op is unaware of the room graph.
|
|
||||||
bool bProtectedStructuralFloor = false;
|
bool bProtectedStructuralFloor = false;
|
||||||
float StructuralFloorMinimumDensity = 0.0f;
|
float StructuralFloorMinimumDensity = 0.0f;
|
||||||
|
|
||||||
@@ -350,14 +348,16 @@ struct FVoxelOpSample
|
|||||||
const FChunkSDFCache* RoomCache = nullptr;
|
const FChunkSDFCache* RoomCache = nullptr;
|
||||||
int32 NearestRoomIndex = INDEX_NONE;
|
int32 NearestRoomIndex = INDEX_NONE;
|
||||||
|
|
||||||
// The common generator tail consumes these values after the block has left the stack. They
|
// The common generator tail consumes the continuous swept-shape metadata after the block has
|
||||||
// are the small, exact subset of FTunnelCoreWorldEvaluation that the tail needs; keeping them
|
// left the stack. Keeping it in the sample avoids re-running the world-space tunnel scan or
|
||||||
// in the sample avoids re-running the world-space tunnel scan or consulting a last-sample TLS
|
// consulting a last-sample TLS value after an op-major evaluation.
|
||||||
// value after an op-major evaluation.
|
|
||||||
bool bHasTunnelCoreWorldEvaluation = false;
|
bool bHasTunnelCoreWorldEvaluation = false;
|
||||||
float TunnelCoreWorldSDF = FLT_MAX;
|
float TunnelCoreWorldSDF = FLT_MAX;
|
||||||
bool bTunnelCoreSupportFloor = false;
|
bool bTunnelCoreSupportFloor = false;
|
||||||
bool bTunnelCoreRoomFloor = false;
|
bool bTunnelCoreRoomFloor = false;
|
||||||
|
bool bHasTunnelCoreSweptFloor = false;
|
||||||
|
float TunnelCoreSweptFloorZ = -FLT_MAX;
|
||||||
|
float TunnelCoreSweptFloorRadius = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -429,8 +429,7 @@ public:
|
|||||||
|
|
||||||
/** Apply the landing/tunnel MC post in one cached passage scan after disturbances. */
|
/** Apply the landing/tunnel MC post in one cached passage scan after disturbances. */
|
||||||
void ApplyPassageStructuralPostsMC(float& Density, float WorldX, float WorldY, float WorldZ,
|
void ApplyPassageStructuralPostsMC(float& Density, float WorldX, float WorldY, float WorldZ,
|
||||||
float BaseDensity, float SealThickness,
|
float BaseDensity, float SealThickness) const;
|
||||||
bool bProtectAuthoredTunnelFloor = false) const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compose the build-authored passage D-floor after generic MC writers. This is the native
|
* Compose the build-authored passage D-floor after generic MC writers. This is the native
|
||||||
|
|||||||
@@ -285,7 +285,6 @@ enum class EVoxelStrateTransition : uint8
|
|||||||
LERPF(TunnelHorizontalBias) \
|
LERPF(TunnelHorizontalBias) \
|
||||||
SNAPF(bTunnelsFlowTowardOrigin) \
|
SNAPF(bTunnelsFlowTowardOrigin) \
|
||||||
LERPF(TunnelEndpointZOffset) \
|
LERPF(TunnelEndpointZOffset) \
|
||||||
SNAPF(bTunnelFloorEnabled) \
|
|
||||||
SNAPF(bTunnelFloorTerracingEnabled) \
|
SNAPF(bTunnelFloorTerracingEnabled) \
|
||||||
LERPF(TunnelFloorTerraceStepHeight) \
|
LERPF(TunnelFloorTerraceStepHeight) \
|
||||||
LERPF(TunnelFloorMaxLedgeHeight) \
|
LERPF(TunnelFloorMaxLedgeHeight) \
|
||||||
@@ -686,10 +685,10 @@ struct VOXELFORGE_API FStrateGenerationParams
|
|||||||
// terraces walls after morphology, while these fields describe the tunnel's own floor profile.
|
// terraces walls after morphology, while these fields describe the tunnel's own floor profile.
|
||||||
// FloorReliefStrength/Frequency above are shared with room floors and are reused here.
|
// FloorReliefStrength/Frequency above are shared with room floors and are reused here.
|
||||||
|
|
||||||
// Whether the swept tunnel receives its authored floor cut. Disable only for an archetype that
|
// Deprecated compatibility field. The tunnel's carved geometry always owns its floor;
|
||||||
// deliberately wants the bare capsule; the structural support backstop remains a measured
|
// serialized values continue to load but are deliberately ignored.
|
||||||
// fallback for malformed/legacy caches and is not a replacement for this shape.
|
UPROPERTY(meta = (DeprecatedProperty,
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Tunnels|Floor")
|
DeprecationMessage = "Ignored; tunnel floor is always authored by tunnel geometry."))
|
||||||
bool bTunnelFloorEnabled = true;
|
bool bTunnelFloorEnabled = true;
|
||||||
|
|
||||||
// Enables deterministic selection of a few dramatic ledges on steep graph-redundant or leaf
|
// Enables deterministic selection of a few dramatic ledges on steep graph-redundant or leaf
|
||||||
@@ -1137,10 +1136,10 @@ constexpr int32 VF_StrateParamListedFieldCount =
|
|||||||
#undef VF_STRATE_PARAM_COUNT_FIELD
|
#undef VF_STRATE_PARAM_COUNT_FIELD
|
||||||
|
|
||||||
static_assert(
|
static_assert(
|
||||||
VF_StrateParamListedFieldCount == 83,
|
VF_StrateParamListedFieldCount == 82,
|
||||||
"FStrateGenerationParams field count changed; update the struct and VF_STRATE_PARAM_FIELDS together.");
|
"FStrateGenerationParams field count changed; update the struct and VF_STRATE_PARAM_FIELDS together.");
|
||||||
static_assert(
|
static_assert(
|
||||||
sizeof(FStrateGenerationParams) == VF_StrateParamListedBytes + 8,
|
sizeof(FStrateGenerationParams) == VF_StrateParamListedBytes + 9,
|
||||||
"FStrateGenerationParams and VF_STRATE_PARAM_FIELDS disagree; update both places and recheck the expected padding.");
|
"FStrateGenerationParams and VF_STRATE_PARAM_FIELDS disagree; update both places and recheck the expected padding.");
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user