#pragma once #include "VoxelTypes.h" 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(FMath::Clamp(Value, MIN_int32, MAX_int32)); } struct FParameters { int32 ClipRadius = 3; int32 MaxClipLevel = 4; int32 RenderDistanceChunks = 0; }; FORCEINLINE int32 FloorDiv(int32 Value, int32 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) { return FIntVector(FloorDiv(Value.X, Divisor), FloorDiv(Value.Y, Divisor), FloorDiv(Value.Z, Divisor)); } FORCEINLINE int32 CeilDiv(int32 Value, int32 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, FIntVector& OutMin, FIntVector& OutMax) { const auto AlignAxis = [Radius, Alignment](int32 CenterAxis, int32& OutAxisMin, int32& OutAxisMax) { const int64 SafeRadius = FMath::Max(0, Radius); const int64 SafeAlignment = FMath::Max(1, Alignment); const int64 BaseMin = static_cast(CenterAxis) - SafeRadius; const int64 BaseEnd = static_cast(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); AlignAxis(Center.Z, OutMin.Z, OutMax.Z); } FORCEINLINE int32 OuterShellRadius(const FParameters& Parameters, int32 Radius, int32 MaxLevel) { if (Parameters.RenderDistanceChunks <= 0) { return Radius; } const int32 SafeLevel = FMath::Clamp(MaxLevel, 0, 8); const int64 Scale = int64{1} << static_cast(SafeLevel); const int64 Numerator = static_cast(Parameters.RenderDistanceChunks) + Scale - 1; const int64 DesiredRadius = Numerator / Scale; return static_cast(FMath::Clamp( FMath::Max(Radius, DesiredRadius), 0, MAX_int32)); } FORCEINLINE void OuterShell(const FParameters& Parameters, int32 Radius, int32 MaxLevel, int32& OutLevel, int32& OutRadius) { OutLevel = MaxLevel; OutRadius = OuterShellRadius(Parameters, Radius, MaxLevel); } FORCEINLINE void ResolveVerticalBounds( int32 CenterChunkZ, int32 ViewDistanceUp, int32 ViewDistanceDown, int32 StrateViewMarginChunks, bool bClampToStrate, bool bHasStrate, int32 StrateTopChunkZ, int32 StrateBottomChunkZ, int32& OutMinChunkZ, int32& OutMaxChunkZ) { OutMinChunkZ = MIN_int32; OutMaxChunkZ = MAX_int32; if (!bClampToStrate) { return; } OutMinChunkZ = ClampInt32(static_cast(CenterChunkZ) - ViewDistanceDown); OutMaxChunkZ = ClampInt32(static_cast(CenterChunkZ) + ViewDistanceUp); if (bHasStrate) { OutMinChunkZ = ClampInt32(static_cast(StrateBottomChunkZ) - StrateViewMarginChunks); OutMaxChunkZ = ClampInt32(static_cast(StrateTopChunkZ) + StrateViewMarginChunks); } } FORCEINLINE void Build(const FIntVector& Center, int32 MinChunkZ, int32 MaxChunkZ, const FParameters& Parameters, TArray& OutTiles) { OutTiles.Reset(); const int32 Radius = FMath::Max(1, Parameters.ClipRadius); const int32 MaxLevel = FMath::Clamp(Parameters.MaxClipLevel, 0, 8); const int32 OuterRadius = OuterShellRadius(Parameters, Radius, MaxLevel); FIntVector PreviousRingMin = FIntVector::ZeroValue; FIntVector PreviousRingMax = FIntVector::ZeroValue; for (int32 Level = 0; Level <= MaxLevel; ++Level) { const int32 Pow = static_cast(uint32{1} << static_cast(Level)); const FIntVector CenterLevel = FloorDiv(Center, Pow); const int32 LevelRadius = (Level == MaxLevel) ? OuterRadius : Radius; // Every finer shell must end on a complete child-pair boundary so a coarser tile is // either wholly inside it or wholly outside it. The outermost ring has no coarser // neighbour and needs no alignment. const int32 RingAlignment = Level < MaxLevel ? 2 : 1; FIntVector RingMin, RingMax; AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax); int64 TileMinZ = RingMin.Z; int64 TileMaxZ = RingMax.Z; if (LevelRadius > Radius && MinChunkZ != MIN_int32) { TileMinZ = FMath::Max(TileMinZ, FloorDiv(MinChunkZ, Pow)); TileMaxZ = FMath::Min(TileMaxZ, FloorDiv(MaxChunkZ, Pow)); } 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(static_cast(X), static_cast(Y), static_cast(Z)); const int64 TileBottomChunkZ = static_cast(Tile.Z) * Pow; const int64 TileTopChunkZ = (static_cast(Tile.Z) + 1) * Pow - 1; if (TileTopChunkZ < MinChunkZ || TileBottomChunkZ > MaxChunkZ) { continue; } if (Level > 0) { const bool bCovered = (2 * static_cast(Tile.X) >= PreviousRingMin.X) && (2 * static_cast(Tile.X) + 1 <= PreviousRingMax.X) && (2 * static_cast(Tile.Y) >= PreviousRingMin.Y) && (2 * static_cast(Tile.Y) + 1 <= PreviousRingMax.Y) && (2 * static_cast(Tile.Z) >= PreviousRingMin.Z) && (2 * static_cast(Tile.Z) + 1 <= PreviousRingMax.Z); if (bCovered) { continue; } } OutTiles.Emplace(Tile, Level); } PreviousRingMin = RingMin; PreviousRingMax = RingMax; } } }