fix: sanitizer sweep across all generators; bound signed shifts/division/overflow at coordinate limits (L61); R31
New verify.py sanitize tier (Clang address,undefined,float-cast-overflow; GCC address,undefined; -fno-sanitize-recover) runs 19 cases per compiler at LOD0-8 across every generator, passages and the crossing: 0 runtime reports; a planted signed-overflow control is caught by both. A static scan fixed INT_MIN negation in floor division, unbounded signed LOD shifts and coordinate-times-extent overflow in VoxelClipmapDesiredTiles.h and VoxelTypes.h (plugin and standalone), and VoxelWorld.cpp, VoxelContentManager.cpp and VoxelDensityVolume.cpp (plugin only, uncompiled here). Supported-input output unchanged (version 211). fast/medium/full PASS. Research R31 (self-checked): far-LOD 4-8 cut plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
c0c204ba50
commit
135957831b
13 files changed
+760
-132
No files matched your search
@@ -4,6 +4,27 @@
|
||||
|
||||
namespace VoxelClipmapDesiredTiles
|
||||
{
|
||||
FORCEINLINE int64 FloorDivWide(int64 Value, int64 Divisor)
|
||||
{
|
||||
if (Divisor <= 0) { return 0; }
|
||||
const int64 Quotient = Value / Divisor;
|
||||
const int64 Remainder = Value % Divisor;
|
||||
return Remainder < 0 ? Quotient - 1 : Quotient;
|
||||
}
|
||||
|
||||
FORCEINLINE int64 CeilDivWide(int64 Value, int64 Divisor)
|
||||
{
|
||||
if (Divisor <= 0) { return 0; }
|
||||
const int64 Quotient = Value / Divisor;
|
||||
const int64 Remainder = Value % Divisor;
|
||||
return Remainder > 0 ? Quotient + 1 : Quotient;
|
||||
}
|
||||
|
||||
FORCEINLINE int32 ClampInt32(int64 Value)
|
||||
{
|
||||
return static_cast<int32>(FMath::Clamp<int64>(Value, MIN_int32, MAX_int32));
|
||||
}
|
||||
|
||||
struct FParameters
|
||||
{
|
||||
int32 ClipRadius = 3;
|
||||
@@ -13,7 +34,10 @@ namespace VoxelClipmapDesiredTiles
|
||||
|
||||
FORCEINLINE int32 FloorDiv(int32 Value, int32 Divisor)
|
||||
{
|
||||
return Value >= 0 ? (Value / Divisor) : -(((-Value) + Divisor - 1) / Divisor);
|
||||
if (Divisor <= 0) { return 0; }
|
||||
const int32 Quotient = Value / Divisor;
|
||||
const int32 Remainder = Value % Divisor;
|
||||
return Remainder < 0 ? Quotient - 1 : Quotient;
|
||||
}
|
||||
|
||||
FORCEINLINE FIntVector FloorDiv(const FIntVector& Value, int32 Divisor)
|
||||
@@ -24,7 +48,10 @@ namespace VoxelClipmapDesiredTiles
|
||||
|
||||
FORCEINLINE int32 CeilDiv(int32 Value, int32 Divisor)
|
||||
{
|
||||
return -FloorDiv(-Value, Divisor);
|
||||
if (Divisor <= 0) { return 0; }
|
||||
const int32 Quotient = Value / Divisor;
|
||||
const int32 Remainder = Value % Divisor;
|
||||
return Remainder > 0 ? Quotient + 1 : Quotient;
|
||||
}
|
||||
|
||||
FORCEINLINE void AlignedRingBounds(const FIntVector& Center, int32 Radius, int32 Alignment,
|
||||
@@ -33,10 +60,12 @@ namespace VoxelClipmapDesiredTiles
|
||||
const auto AlignAxis = [Radius, Alignment](int32 CenterAxis,
|
||||
int32& OutAxisMin, int32& OutAxisMax)
|
||||
{
|
||||
const int32 BaseMin = CenterAxis - Radius;
|
||||
const int32 BaseEnd = CenterAxis + Radius + 1;
|
||||
OutAxisMin = FloorDiv(BaseMin, Alignment) * Alignment;
|
||||
OutAxisMax = CeilDiv(BaseEnd, Alignment) * Alignment - 1;
|
||||
const int64 SafeRadius = FMath::Max<int64>(0, Radius);
|
||||
const int64 SafeAlignment = FMath::Max<int64>(1, Alignment);
|
||||
const int64 BaseMin = static_cast<int64>(CenterAxis) - SafeRadius;
|
||||
const int64 BaseEnd = static_cast<int64>(CenterAxis) + SafeRadius + 1;
|
||||
OutAxisMin = ClampInt32(FloorDivWide(BaseMin, SafeAlignment) * SafeAlignment);
|
||||
OutAxisMax = ClampInt32(CeilDivWide(BaseEnd, SafeAlignment) * SafeAlignment - 1);
|
||||
};
|
||||
AlignAxis(Center.X, OutMin.X, OutMax.X);
|
||||
AlignAxis(Center.Y, OutMin.Y, OutMax.Y);
|
||||
@@ -49,8 +78,13 @@ namespace VoxelClipmapDesiredTiles
|
||||
{
|
||||
return Radius;
|
||||
}
|
||||
return FMath::Max(Radius,
|
||||
(Parameters.RenderDistanceChunks + (1 << MaxLevel) - 1) >> MaxLevel);
|
||||
const int32 SafeLevel = FMath::Clamp(MaxLevel, 0, 8);
|
||||
const int64 Scale = static_cast<int64>(uint32{1} << static_cast<uint32>(SafeLevel));
|
||||
const int64 Numerator = static_cast<int64>(Parameters.RenderDistanceChunks)
|
||||
+ Scale - 1;
|
||||
const int64 DesiredRadius = Numerator / Scale;
|
||||
return static_cast<int32>(FMath::Clamp<int64>(
|
||||
FMath::Max<int64>(Radius, DesiredRadius), 0, MAX_int32));
|
||||
}
|
||||
|
||||
FORCEINLINE void OuterShell(const FParameters& Parameters, int32 Radius, int32 MaxLevel,
|
||||
@@ -73,12 +107,12 @@ namespace VoxelClipmapDesiredTiles
|
||||
return;
|
||||
}
|
||||
|
||||
OutMinChunkZ = CenterChunkZ - ViewDistanceDown;
|
||||
OutMaxChunkZ = CenterChunkZ + ViewDistanceUp;
|
||||
OutMinChunkZ = ClampInt32(static_cast<int64>(CenterChunkZ) - ViewDistanceDown);
|
||||
OutMaxChunkZ = ClampInt32(static_cast<int64>(CenterChunkZ) + ViewDistanceUp);
|
||||
if (bHasStrate)
|
||||
{
|
||||
OutMinChunkZ = StrateBottomChunkZ - StrateViewMarginChunks;
|
||||
OutMaxChunkZ = StrateTopChunkZ + StrateViewMarginChunks;
|
||||
OutMinChunkZ = ClampInt32(static_cast<int64>(StrateBottomChunkZ) - StrateViewMarginChunks);
|
||||
OutMaxChunkZ = ClampInt32(static_cast<int64>(StrateTopChunkZ) + StrateViewMarginChunks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +128,7 @@ namespace VoxelClipmapDesiredTiles
|
||||
|
||||
for (int32 Level = 0; Level <= MaxLevel; ++Level)
|
||||
{
|
||||
const int32 Pow = 1 << Level;
|
||||
const int32 Pow = static_cast<int32>(uint32{1} << static_cast<uint32>(Level));
|
||||
const FIntVector CenterLevel = FloorDiv(Center, Pow);
|
||||
const int32 LevelRadius = (Level == MaxLevel) ? OuterRadius : Radius;
|
||||
|
||||
@@ -105,24 +139,23 @@ namespace VoxelClipmapDesiredTiles
|
||||
FIntVector RingMin, RingMax;
|
||||
AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax);
|
||||
|
||||
int32 DeltaZMin = RingMin.Z - CenterLevel.Z;
|
||||
int32 DeltaZMax = RingMax.Z - CenterLevel.Z;
|
||||
int64 TileMinZ = RingMin.Z;
|
||||
int64 TileMaxZ = RingMax.Z;
|
||||
if (LevelRadius > Radius && MinChunkZ != MIN_int32)
|
||||
{
|
||||
DeltaZMin = FMath::Max(DeltaZMin, FloorDiv(MinChunkZ, Pow) - CenterLevel.Z);
|
||||
DeltaZMax = FMath::Min(DeltaZMax, FloorDiv(MaxChunkZ, Pow) - CenterLevel.Z);
|
||||
TileMinZ = FMath::Max<int64>(TileMinZ, FloorDiv(MinChunkZ, Pow));
|
||||
TileMaxZ = FMath::Min<int64>(TileMaxZ, FloorDiv(MaxChunkZ, Pow));
|
||||
}
|
||||
|
||||
for (int32 DeltaZ = DeltaZMin; DeltaZ <= DeltaZMax; ++DeltaZ)
|
||||
for (int32 DeltaY = RingMin.Y - CenterLevel.Y;
|
||||
DeltaY <= RingMax.Y - CenterLevel.Y; ++DeltaY)
|
||||
for (int32 DeltaX = RingMin.X - CenterLevel.X;
|
||||
DeltaX <= RingMax.X - CenterLevel.X; ++DeltaX)
|
||||
for (int64 Z = TileMinZ; Z <= TileMaxZ; ++Z)
|
||||
for (int64 Y = RingMin.Y; Y <= RingMax.Y; ++Y)
|
||||
for (int64 X = RingMin.X; X <= RingMax.X; ++X)
|
||||
{
|
||||
const FIntVector Tile = CenterLevel + FIntVector(DeltaX, DeltaY, DeltaZ);
|
||||
const int32 TileMinZ = Tile.Z << Level;
|
||||
const int32 TileMaxZ = ((Tile.Z + 1) << Level) - 1;
|
||||
if (TileMaxZ < MinChunkZ || TileMinZ > MaxChunkZ)
|
||||
const FIntVector Tile(static_cast<int32>(X), static_cast<int32>(Y),
|
||||
static_cast<int32>(Z));
|
||||
const int64 TileBottomChunkZ = static_cast<int64>(Tile.Z) * Pow;
|
||||
const int64 TileTopChunkZ = (static_cast<int64>(Tile.Z) + 1) * Pow - 1;
|
||||
if (TileTopChunkZ < MinChunkZ || TileBottomChunkZ > MaxChunkZ)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -130,12 +163,12 @@ namespace VoxelClipmapDesiredTiles
|
||||
if (Level > 0)
|
||||
{
|
||||
const bool bCovered =
|
||||
(2 * Tile.X >= PreviousRingMin.X)
|
||||
&& (2 * Tile.X + 1 <= PreviousRingMax.X)
|
||||
&& (2 * Tile.Y >= PreviousRingMin.Y)
|
||||
&& (2 * Tile.Y + 1 <= PreviousRingMax.Y)
|
||||
&& (2 * Tile.Z >= PreviousRingMin.Z)
|
||||
&& (2 * Tile.Z + 1 <= PreviousRingMax.Z);
|
||||
(2 * static_cast<int64>(Tile.X) >= PreviousRingMin.X)
|
||||
&& (2 * static_cast<int64>(Tile.X) + 1 <= PreviousRingMax.X)
|
||||
&& (2 * static_cast<int64>(Tile.Y) >= PreviousRingMin.Y)
|
||||
&& (2 * static_cast<int64>(Tile.Y) + 1 <= PreviousRingMax.Y)
|
||||
&& (2 * static_cast<int64>(Tile.Z) >= PreviousRingMin.Z)
|
||||
&& (2 * static_cast<int64>(Tile.Z) + 1 <= PreviousRingMax.Z);
|
||||
if (bCovered)
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -559,9 +559,13 @@ int32 UVoxelContentManager::RegionSize() const
|
||||
// truncation, a negative-coord cell is queued under one region but routed back to another → its march
|
||||
// result is dropped → blank chunk. Truncation == floor for A>=0 and for R==1, which is exactly why only
|
||||
// NEGATIVE coordinates at R>=2 were affected. R (region size) is always > 0 here.
|
||||
static FORCEINLINE int32 FloorDivPos(int32 A, int32 R)
|
||||
static FORCEINLINE int32 FloorDivPos(int64 A, int32 R)
|
||||
{
|
||||
return (A >= 0) ? (A / R) : -(((-A) + R - 1) / R);
|
||||
if (R <= 0) { return 0; }
|
||||
const int64 Quotient = A / R;
|
||||
const int64 Remainder = A % R;
|
||||
return static_cast<int32>(FMath::Clamp<int64>(
|
||||
Remainder < 0 ? Quotient - 1 : Quotient, MIN_int32, MAX_int32));
|
||||
}
|
||||
|
||||
static FORCEINLINE FIntPoint CellToRegion(const FIntPoint& Cell, int32 R)
|
||||
@@ -573,10 +577,10 @@ static FORCEINLINE FIntPoint CellToRegion(const FIntPoint& Cell, int32 R)
|
||||
static FORCEINLINE bool IsRegionDesired(const FIntPoint& Region, const FIntPoint& PlayerCell,
|
||||
int32 FarR, int32 R)
|
||||
{
|
||||
const int32 MinX = FloorDivPos(PlayerCell.X - FarR, R);
|
||||
const int32 MaxX = FloorDivPos(PlayerCell.X + FarR, R);
|
||||
const int32 MinY = FloorDivPos(PlayerCell.Y - FarR, R);
|
||||
const int32 MaxY = FloorDivPos(PlayerCell.Y + FarR, R);
|
||||
const int32 MinX = FloorDivPos(static_cast<int64>(PlayerCell.X) - FarR, R);
|
||||
const int32 MaxX = FloorDivPos(static_cast<int64>(PlayerCell.X) + FarR, R);
|
||||
const int32 MinY = FloorDivPos(static_cast<int64>(PlayerCell.Y) - FarR, R);
|
||||
const int32 MaxY = FloorDivPos(static_cast<int64>(PlayerCell.Y) + FarR, R);
|
||||
return Region.X >= MinX && Region.X <= MaxX && Region.Y >= MinY && Region.Y <= MaxY;
|
||||
}
|
||||
|
||||
@@ -1102,17 +1106,22 @@ void UVoxelContentManager::RebuildDesiredCells(FDecoGrid& G, const FIntPoint& Pl
|
||||
const int32 R = RegionSize();
|
||||
|
||||
// Desired regions = every region whose footprint touches the radius-FarR cell box around the player.
|
||||
const FIntPoint RMin(FloorDivPos(PlayerCell.X - FarR, R),
|
||||
FloorDivPos(PlayerCell.Y - FarR, R));
|
||||
const FIntPoint RMax(FloorDivPos(PlayerCell.X + FarR, R),
|
||||
FloorDivPos(PlayerCell.Y + FarR, R));
|
||||
const FIntPoint RMin(FloorDivPos(static_cast<int64>(PlayerCell.X) - FarR, R),
|
||||
FloorDivPos(static_cast<int64>(PlayerCell.Y) - FarR, R));
|
||||
const FIntPoint RMax(FloorDivPos(static_cast<int64>(PlayerCell.X) + FarR, R),
|
||||
FloorDivPos(static_cast<int64>(PlayerCell.Y) + FarR, R));
|
||||
|
||||
TSet<FIntPoint> DesiredRegions;
|
||||
DesiredRegions.Reserve((RMax.X - RMin.X + 1) * (RMax.Y - RMin.Y + 1));
|
||||
for (int32 ry = RMin.Y; ry <= RMax.Y; ++ry)
|
||||
for (int32 rx = RMin.X; rx <= RMax.X; ++rx)
|
||||
constexpr int64 MaxRegionReserve = 4096;
|
||||
const int64 WidthX = static_cast<int64>(RMax.X) - RMin.X + 1;
|
||||
const int64 WidthY = static_cast<int64>(RMax.Y) - RMin.Y + 1;
|
||||
const int64 ReserveCount = WidthY > 0 && WidthX > MaxRegionReserve / WidthY
|
||||
? MaxRegionReserve : FMath::Max<int64>(0, WidthX * WidthY);
|
||||
DesiredRegions.Reserve(static_cast<int32>(ReserveCount));
|
||||
for (int64 ry = RMin.Y; ry <= RMax.Y; ++ry)
|
||||
for (int64 rx = RMin.X; rx <= RMax.X; ++rx)
|
||||
{
|
||||
DesiredRegions.Add(FIntPoint(rx, ry));
|
||||
DesiredRegions.Add(FIntPoint(static_cast<int32>(rx), static_cast<int32>(ry)));
|
||||
}
|
||||
|
||||
// Unload loaded regions no longer desired (plain DestroyComponent — no per-instance removal).
|
||||
@@ -1464,8 +1473,15 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
|
||||
const uint32 CH = VoxelHash::Mix(H ^ (0x00C0FFEEu + (uint32)ci * 0x9E3779B1u));
|
||||
if (VoxelHash::ToFloat01(CH) > Comp.Probability) continue;
|
||||
|
||||
const int32 Span = FMath::Max(0, Comp.CountMax - Comp.CountMin);
|
||||
const int32 Count = Comp.CountMin + (int32)(VoxelHash::ToFloat01(VoxelHash::Mix(CH ^ 0x1234u)) * (float)(Span + 1));
|
||||
const int64 Span = FMath::Max<int64>(0,
|
||||
static_cast<int64>(Comp.CountMax) - Comp.CountMin);
|
||||
const uint64 CountRange = static_cast<uint64>(Span) + 1u;
|
||||
const float CountOffset = VoxelHash::ToFloat01(
|
||||
VoxelHash::Mix(CH ^ 0x1234u)) * static_cast<float>(CountRange);
|
||||
const int64 CountWide = static_cast<int64>(Comp.CountMin)
|
||||
+ static_cast<int64>(CountOffset);
|
||||
const int32 Count = static_cast<int32>(FMath::Clamp<int64>(
|
||||
CountWide, MIN_int32, MAX_int32));
|
||||
for (int32 ii = 0; ii < Count; ++ii)
|
||||
{
|
||||
if (CompBudget <= 0) break;
|
||||
@@ -1514,8 +1530,15 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
|
||||
const uint32 SH = VoxelHash::Mix(IH ^ (0x0000544Bu + (uint32)si * 0x27D4EB2Fu));
|
||||
if (VoxelHash::ToFloat01(SH) > Sub.Probability) continue;
|
||||
|
||||
const int32 SubSpan = FMath::Max(0, Sub.CountMax - Sub.CountMin);
|
||||
const int32 SubCount = Sub.CountMin + (int32)(VoxelHash::ToFloat01(VoxelHash::Mix(SH ^ 0x1234u)) * (float)(SubSpan + 1));
|
||||
const int64 SubSpan = FMath::Max<int64>(0,
|
||||
static_cast<int64>(Sub.CountMax) - Sub.CountMin);
|
||||
const uint64 SubCountRange = static_cast<uint64>(SubSpan) + 1u;
|
||||
const float SubCountOffset = VoxelHash::ToFloat01(
|
||||
VoxelHash::Mix(SH ^ 0x1234u)) * static_cast<float>(SubCountRange);
|
||||
const int64 SubCountWide = static_cast<int64>(Sub.CountMin)
|
||||
+ static_cast<int64>(SubCountOffset);
|
||||
const int32 SubCount = static_cast<int32>(FMath::Clamp<int64>(
|
||||
SubCountWide, MIN_int32, MAX_int32));
|
||||
for (int32 sj = 0; sj < SubCount; ++sj)
|
||||
{
|
||||
if (CompBudget <= 0) break;
|
||||
|
||||
@@ -155,9 +155,11 @@ int32 UVoxelDensityVolume::NumLevels() const
|
||||
|
||||
FORCEINLINE int32 UVoxelDensityVolume::FloorDiv(int32 A, int32 B)
|
||||
{
|
||||
// True floor division (B > 0). FMath::DivideAndRoundDown truncates toward zero for negatives —
|
||||
// a footgun the content manager hit too (see FloorDivPos there). Cells span the origin, so floor.
|
||||
return (A >= 0) ? (A / B) : -(((-A) + B - 1) / B);
|
||||
// True floor division (B > 0). Remainder adjustment handles INT_MIN without negating it.
|
||||
if (B <= 0) { return 0; }
|
||||
const int32 Quotient = A / B;
|
||||
const int32 Remainder = A % B;
|
||||
return Remainder < 0 ? Quotient - 1 : Quotient;
|
||||
}
|
||||
|
||||
FORCEINLINE uint8 UVoxelDensityVolume::Quantize(float MCDensity)
|
||||
@@ -180,7 +182,8 @@ void UVoxelDensityVolume::EnsureAllocated()
|
||||
for (int32 L = 0; L < N; ++L)
|
||||
{
|
||||
FClipLevel& Lv = Levels[L];
|
||||
Lv.Step = 1 << L;
|
||||
Lv.Step = static_cast<int32>(uint32{1}
|
||||
<< static_cast<uint32>(FMath::Clamp(L, 0, 8)));
|
||||
Lv.OriginCells = FIntVector(INT32_MAX, INT32_MAX, INT32_MAX);
|
||||
Lv.bHasData = false;
|
||||
Lv.Density.SetNumZeroed(Count); // start all-air (0)
|
||||
@@ -632,7 +635,8 @@ void UVoxelDensityVolume::ProcessOneFill(const FPendingFill& F)
|
||||
if (!Gen) return;
|
||||
if (bShuttingDown.load(std::memory_order_relaxed) || bFillThreadStop.load(std::memory_order_relaxed)) return;
|
||||
|
||||
const int32 Step = 1 << F.Level;
|
||||
const int32 Step = static_cast<int32>(uint32{1}
|
||||
<< static_cast<uint32>(FMath::Clamp(F.Level, 0, 8)));
|
||||
FFillResult R;
|
||||
R.Level = F.Level;
|
||||
R.Epoch = F.Epoch;
|
||||
|
||||
@@ -82,6 +82,17 @@
|
||||
#include <atomic>
|
||||
#include <unordered_set>
|
||||
|
||||
static FORCEINLINE int32 VF_ClipLevelScale(int32 Level)
|
||||
{
|
||||
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
|
||||
return static_cast<int32>(uint32{1} << SafeLevel);
|
||||
}
|
||||
static FORCEINLINE int32 VF_ClipLevelExtentVoxels(int32 Level)
|
||||
{
|
||||
return static_cast<int32>(static_cast<uint32>(CHUNK_SIZE)
|
||||
* static_cast<uint32>(VF_ClipLevelScale(Level)));
|
||||
}
|
||||
|
||||
static bool VF_TileOverlapsVoxelBounds(const FVoxelTileKey& Tile,
|
||||
const FVector& BoundsMin, const FVector& BoundsMax);
|
||||
static bool VF_TileFootprintsOverlap(const FVoxelTileKey& A, const FVoxelTileKey& B);
|
||||
@@ -6240,17 +6251,19 @@ static void VF_AppendTouchingAncestors(
|
||||
const FVoxelTileKey& Tile, int32 AncestorLevel, CandidateArray& OutCandidates)
|
||||
{
|
||||
if (AncestorLevel <= Tile.Level) { return; }
|
||||
const int32 Divisor = 1 << (AncestorLevel - Tile.Level);
|
||||
const auto FloorDiv = [Divisor](int32 Value)
|
||||
const int32 Divisor = VF_ClipLevelScale(AncestorLevel - Tile.Level);
|
||||
const auto FloorDiv = [Divisor](int64 Value)
|
||||
{
|
||||
return Value >= 0 ? Value / Divisor : -(((-Value) + Divisor - 1) / Divisor);
|
||||
const int64 Quotient = Value / Divisor;
|
||||
const int64 Remainder = Value % Divisor;
|
||||
return static_cast<int32>(Remainder < 0 ? Quotient - 1 : Quotient);
|
||||
};
|
||||
const auto AxisCandidates = [&FloorDiv, Divisor](int32 Value)
|
||||
{
|
||||
TArray<int32, TInlineAllocator<2>> Candidates;
|
||||
int32 First = FloorDiv(Value);
|
||||
if ((Value % Divisor) == 0) { --First; }
|
||||
const int32 Last = FloorDiv(Value + 1);
|
||||
const int32 Last = FloorDiv(static_cast<int64>(Value) + 1);
|
||||
for (int32 Coord = First; Coord <= Last; ++Coord) { Candidates.Add(Coord); }
|
||||
return Candidates;
|
||||
};
|
||||
@@ -6338,7 +6351,7 @@ void AVoxelWorld::UpdateVisualStalenessAudit()
|
||||
const FVector& WorldPosition, int32 Level, auto& OutCandidates)
|
||||
{
|
||||
const FVector LocalVoxel = WorldToLocalVoxel(WorldPosition);
|
||||
const float TileExtentVoxels = static_cast<float>(CHUNK_SIZE * (1 << Level));
|
||||
const float TileExtentVoxels = static_cast<float>(VF_ClipLevelExtentVoxels(Level));
|
||||
TArray<int32, TInlineAllocator<2>> XCandidates;
|
||||
TArray<int32, TInlineAllocator<2>> YCandidates;
|
||||
TArray<int32, TInlineAllocator<2>> ZCandidates;
|
||||
@@ -7182,7 +7195,7 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
{
|
||||
const FIntVector Min = SteadyLevelMins.FindChecked(Level);
|
||||
const FIntVector Max = SteadyLevelMaxs.FindChecked(Level);
|
||||
const int32 ExtentVoxels = CHUNK_SIZE << Level;
|
||||
const int32 ExtentVoxels = VF_ClipLevelExtentVoxels(Level);
|
||||
const double TileExtentMetres = static_cast<double>(ExtentVoxels) * VOXEL_SIZE / 100.0;
|
||||
const double SpanX = static_cast<double>(Max.X - Min.X + 1) * TileExtentMetres;
|
||||
const double SpanY = static_cast<double>(Max.Y - Min.Y + 1) * TileExtentMetres;
|
||||
@@ -7663,10 +7676,20 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
|
||||
};
|
||||
for (const VoxelForgeCore::TileKey& Key : InitialDesired)
|
||||
{
|
||||
const int32 Extent = 32 << Key.lod;
|
||||
const int32 OriginX = Key.coordinate.x * Extent;
|
||||
const int32 OriginY = Key.coordinate.y * Extent;
|
||||
const int32 OriginZ = Key.coordinate.z * Extent;
|
||||
const int32 Extent = VF_ClipLevelExtentVoxels(Key.lod);
|
||||
const int64 OriginXWide = static_cast<int64>(Key.coordinate.x) * Extent;
|
||||
const int64 OriginYWide = static_cast<int64>(Key.coordinate.y) * Extent;
|
||||
const int64 OriginZWide = static_cast<int64>(Key.coordinate.z) * Extent;
|
||||
if (OriginXWide < MIN_int32 || OriginYWide < MIN_int32 || OriginZWide < MIN_int32
|
||||
|| OriginXWide + Extent > MAX_int32 || OriginYWide + Extent > MAX_int32
|
||||
|| OriginZWide + Extent > MAX_int32)
|
||||
{
|
||||
OutFailureReason = TEXT("Core initial clipmap exceeds the supported voxel-coordinate range.");
|
||||
return false;
|
||||
}
|
||||
const int32 OriginX = static_cast<int32>(OriginXWide);
|
||||
const int32 OriginY = static_cast<int32>(OriginYWide);
|
||||
const int32 OriginZ = static_cast<int32>(OriginZWide);
|
||||
IncludeExtent(ClipMin.x, ClipMax.x, OriginX, Extent);
|
||||
IncludeExtent(ClipMin.y, ClipMax.y, OriginY, Extent);
|
||||
IncludeExtent(ClipMin.z, ClipMax.z, OriginZ, Extent);
|
||||
@@ -10074,8 +10097,11 @@ void AVoxelWorld::SyncRemeshTile(const FVoxelTileKey& Tile)
|
||||
{
|
||||
BandChunkLo = MeshBandChunkLo;
|
||||
BandChunkHi = MeshBandChunkHi;
|
||||
BandVoxLo = MeshBandChunkLo * CHUNK_SIZE;
|
||||
BandVoxHi = (MeshBandChunkHi + 1) * CHUNK_SIZE - 1;
|
||||
BandVoxLo = static_cast<int32>(FMath::Clamp<int64>(
|
||||
static_cast<int64>(MeshBandChunkLo) * CHUNK_SIZE, MIN_int32, MAX_int32));
|
||||
BandVoxHi = static_cast<int32>(FMath::Clamp<int64>(
|
||||
(static_cast<int64>(MeshBandChunkHi) + 1) * CHUNK_SIZE - 1,
|
||||
MIN_int32, MAX_int32));
|
||||
}
|
||||
|
||||
FChunkResult Result;
|
||||
@@ -10238,7 +10264,7 @@ void AVoxelWorld::ReconcileReadyTransitionVisibility()
|
||||
ReadyToHide.Reserve(TileComponents.Num());
|
||||
auto GetAncestorAtLevel = [](const FVoxelTileKey& Tile, int32 AncestorLevel)
|
||||
{
|
||||
const int32 Divisor = 1 << (AncestorLevel - Tile.Level);
|
||||
const int32 Divisor = VF_ClipLevelScale(AncestorLevel - Tile.Level);
|
||||
return FVoxelTileKey(VoxelClipmapDesiredTiles::FloorDiv(Tile.Coord, Divisor), AncestorLevel);
|
||||
};
|
||||
for (const TPair<FVoxelTileKey, URealtimeMeshComponent*>& Pair : TileComponents)
|
||||
@@ -10361,7 +10387,10 @@ void AVoxelWorld::ReconcileReadyTransitionVisibility()
|
||||
// Integer floor-division (correct for negatives), scalar + vector.
|
||||
static FORCEINLINE int32 VF_FloorDiv(int32 V, int32 D)
|
||||
{
|
||||
return V >= 0 ? (V / D) : -(((-V) + D - 1) / D);
|
||||
if (D <= 0) { return 0; }
|
||||
const int32 Quotient = V / D;
|
||||
const int32 Remainder = V % D;
|
||||
return Remainder < 0 ? Quotient - 1 : Quotient;
|
||||
}
|
||||
static FORCEINLINE FIntVector VF_FloorDiv(const FIntVector& V, int32 D)
|
||||
{
|
||||
@@ -11441,13 +11470,13 @@ bool AVoxelWorld::IsTileInClipRange(const FVoxelTileKey& Tile, const FIntVector&
|
||||
{
|
||||
VF_OuterShell(Settings, R, MaxLevelBase, MaxLevel, ROuter);
|
||||
}
|
||||
const int32 PowMax = 1 << MaxLevel;
|
||||
const int32 PowMax = VF_ClipLevelScale(MaxLevel);
|
||||
const FIntVector CMax = VF_FloorDiv(Center, PowMax);
|
||||
|
||||
FVector CV;
|
||||
if (bUseVoxelForgeCore)
|
||||
{
|
||||
const double TileExtentVoxels = static_cast<double>(CHUNK_SIZE << Tile.Level);
|
||||
const double TileExtentVoxels = static_cast<double>(VF_ClipLevelExtentVoxels(Tile.Level));
|
||||
CV = FVector(
|
||||
(static_cast<double>(Tile.Coord.X) + 0.5) * TileExtentVoxels,
|
||||
(static_cast<double>(Tile.Coord.Y) + 0.5) * TileExtentVoxels,
|
||||
@@ -11463,9 +11492,9 @@ bool AVoxelWorld::IsTileInClipRange(const FVoxelTileKey& Tile, const FIntVector&
|
||||
VF_FloorDiv(FMath::FloorToInt(CV.Y), SizeMax),
|
||||
VF_FloorDiv(FMath::FloorToInt(CV.Z), SizeMax));
|
||||
|
||||
return FMath::Abs(TMax.X - CMax.X) <= ROuter
|
||||
&& FMath::Abs(TMax.Y - CMax.Y) <= ROuter
|
||||
&& FMath::Abs(TMax.Z - CMax.Z) <= ROuter;
|
||||
return FMath::Abs(static_cast<int64>(TMax.X) - CMax.X) <= ROuter
|
||||
&& FMath::Abs(static_cast<int64>(TMax.Y) - CMax.Y) <= ROuter
|
||||
&& FMath::Abs(static_cast<int64>(TMax.Z) - CMax.Z) <= ROuter;
|
||||
}
|
||||
|
||||
int32 AVoxelWorld::GetMaxConcurrentTasks() const
|
||||
@@ -12058,7 +12087,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
LevelSummaries.Num(), Leavers.Num(), CollisionOnlyTiles.Num()));
|
||||
for (const FDesiredLevelSummary& Summary : LevelSummaries)
|
||||
{
|
||||
const int32 ExtentVoxels = CHUNK_SIZE << Summary.Level;
|
||||
const int32 ExtentVoxels = VF_ClipLevelExtentVoxels(Summary.Level);
|
||||
const double TileExtentMetres = static_cast<double>(ExtentVoxels) * VOXEL_SIZE / 100.0;
|
||||
const double SpanX = static_cast<double>(Summary.Max.X - Summary.Min.X + 1) * TileExtentMetres;
|
||||
const double SpanY = static_cast<double>(Summary.Max.Y - Summary.Min.Y + 1) * TileExtentMetres;
|
||||
@@ -12109,8 +12138,9 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
for (const FVoxelTileKey& T : LoadedTiles)
|
||||
{
|
||||
if (T.Level < CutMin) continue;
|
||||
const int32 CLo = T.Coord.Z << T.Level;
|
||||
const int32 CHi = ((T.Coord.Z + 1) << T.Level) - 1;
|
||||
const int64 LevelScale = VF_ClipLevelScale(T.Level);
|
||||
const int64 CLo = static_cast<int64>(T.Coord.Z) * LevelScale;
|
||||
const int64 CHi = (static_cast<int64>(T.Coord.Z) + 1) * LevelScale - 1;
|
||||
const bool bInOld = CLo >= MeshBandChunkLo && CHi <= MeshBandChunkHi;
|
||||
const bool bInNew = CLo >= NewLo && CHi <= NewHi;
|
||||
const bool bOutOld = CHi < MeshBandChunkLo || CLo > MeshBandChunkHi;
|
||||
@@ -12330,7 +12360,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
}
|
||||
Heading = Heading.GetSafeNormal();
|
||||
const FVector PlayerLocalCm = WorldToLocalCm(CenterPosition);
|
||||
const double TileSizeCm = static_cast<double>(CHUNK_SIZE << Level)
|
||||
const double TileSizeCm = static_cast<double>(VF_ClipLevelExtentVoxels(Level))
|
||||
* GetActiveVoxelSizeCm();
|
||||
LevelTiles.Sort([&](const FVoxelTileKey& A, const FVoxelTileKey& B)
|
||||
{
|
||||
@@ -12788,7 +12818,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
const int32 FullRes = GVoxelForgeTestFullResClipLevels >= 0
|
||||
? FMath::Clamp(GVoxelForgeTestFullResClipLevels, 1, 9)
|
||||
: (Settings ? FMath::Max(1, Settings->FullResClipLevels) : 2);
|
||||
const int32 Extent = CHUNK_SIZE << Tile.Level;
|
||||
const int32 Extent = VF_ClipLevelExtentVoxels(Tile.Level);
|
||||
|
||||
const int32 Cells = (Tile.Level < FullRes)
|
||||
? CHUNK_SIZE
|
||||
@@ -12853,7 +12883,9 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
// et mélanges de matériaux qu'avant la coupe) — la tuile ne peut rendre que des artefacts
|
||||
// ⇒ on n'émet RIEN. Résultat vide via ProcessQueue (bookkeeping normal : PendingTiles,
|
||||
// LoadedTiles, epoch) ; jamais figé — le changement de bande re-queue via BandRemeshQueue.
|
||||
if (Step > (BandChunkHi - BandChunkLo + 1) * CHUNK_SIZE)
|
||||
const int64 BandHeightVoxels =
|
||||
(static_cast<int64>(BandChunkHi) - BandChunkLo + 1) * CHUNK_SIZE;
|
||||
if (static_cast<int64>(Step) > BandHeightVoxels)
|
||||
{
|
||||
FChunkResult Empty;
|
||||
Empty.Tile = Tile;
|
||||
@@ -13988,7 +14020,8 @@ bool AVoxelWorld::ApplyMeshToTile(FChunkResult& Result)
|
||||
// Convertir les bornes physiques de tuile vers les strates owner, y compris les rapports fractionnaires.
|
||||
const double OwnerChunksPerCoreTile = static_cast<double>(GetActiveVoxelSizeCm())
|
||||
/ VOXEL_SIZE;
|
||||
const double TileExtentCoreVoxels = static_cast<double>(CHUNK_SIZE << Tile.Level);
|
||||
const double TileExtentCoreVoxels =
|
||||
static_cast<double>(VF_ClipLevelExtentVoxels(Tile.Level));
|
||||
const int32 MinOwnerChunkX = FMath::FloorToInt(
|
||||
Tile.Coord.X * TileExtentCoreVoxels * OwnerChunksPerCoreTile / CHUNK_SIZE);
|
||||
const int32 MinOwnerChunkY = FMath::FloorToInt(
|
||||
@@ -15180,7 +15213,7 @@ void AVoxelWorld::RemeshDirtyChunks(const TArray<FIntVector>& DirtyCoords, const
|
||||
// BackgroundHigh; the old component remains visible until its post-edit replacement is applied.
|
||||
auto TileCoversDirtyCoord = [&DirtyCoords](const FVoxelTileKey& Tile) -> bool
|
||||
{
|
||||
const int32 Span = 1 << Tile.Level;
|
||||
const int32 Span = VF_ClipLevelScale(Tile.Level);
|
||||
const FIntVector MinChunk = Tile.Coord * Span;
|
||||
const FIntVector MaxChunk = MinChunk + FIntVector(Span, Span, Span);
|
||||
for (const FIntVector& Coord : DirtyCoords)
|
||||
|
||||
@@ -332,16 +332,33 @@ struct FVoxelTileKey
|
||||
FVoxelTileKey(const FIntVector& InCoord, int32 InLevel) : Coord(InCoord), Level(InLevel) {}
|
||||
|
||||
// Cell size (sampling step) in voxels, and tile extent in voxels per axis.
|
||||
int32 StepVoxels() const { return 1 << Level; }
|
||||
int32 ExtentVoxels() const { return CHUNK_SIZE << Level; }
|
||||
int32 StepVoxels() const
|
||||
{
|
||||
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
|
||||
return static_cast<int32>(uint32{1} << SafeLevel);
|
||||
}
|
||||
int32 ExtentVoxels() const
|
||||
{
|
||||
const uint32 SafeLevel = static_cast<uint32>(FMath::Clamp(Level, 0, 8));
|
||||
return static_cast<int32>(static_cast<uint32>(CHUNK_SIZE) * (uint32{1} << SafeLevel));
|
||||
}
|
||||
|
||||
// Min-corner of the tile in VOXEL coords (what the mesher takes).
|
||||
FIntVector OriginVoxels() const { return Coord * (CHUNK_SIZE << Level); }
|
||||
FIntVector OriginVoxels() const
|
||||
{
|
||||
const int64 Extent = ExtentVoxels();
|
||||
const auto ScaleAxis = [Extent](int32 Axis)
|
||||
{
|
||||
return static_cast<int32>(FMath::Clamp<int64>(
|
||||
static_cast<int64>(Axis) * Extent, MIN_int32, MAX_int32));
|
||||
};
|
||||
return FIntVector(ScaleAxis(Coord.X), ScaleAxis(Coord.Y), ScaleAxis(Coord.Z));
|
||||
}
|
||||
|
||||
// Tile centre in world cm (for distance sorting / LOD selection).
|
||||
FVector CenterCm() const
|
||||
{
|
||||
const double Ext = (double)(CHUNK_SIZE << Level) * (double)VOXEL_SIZE;
|
||||
const double Ext = static_cast<double>(ExtentVoxels()) * static_cast<double>(VOXEL_SIZE);
|
||||
return FVector((Coord.X + 0.5) * Ext, (Coord.Y + 0.5) * Ext, (Coord.Z + 0.5) * Ext);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user