From c7886c555c38f854195a085cb519e1715f813c2d Mon Sep 17 00:00:00 2001 From: Steve Streeting Date: Fri, 3 Nov 2023 16:35:13 +0000 Subject: [PATCH] Add Halton Sequence based balanced random distribution --- Source/StevesUEHelpers/Private/StevesBPL.cpp | 5 + Source/StevesUEHelpers/Public/StevesBPL.h | 21 ++- .../Public/StevesBalancedRandomStream.h | 165 ++++++++++++++++++ 3 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 Source/StevesUEHelpers/Public/StevesBalancedRandomStream.h diff --git a/Source/StevesUEHelpers/Private/StevesBPL.cpp b/Source/StevesUEHelpers/Private/StevesBPL.cpp index 954c0cf..ea0ebf1 100644 --- a/Source/StevesUEHelpers/Private/StevesBPL.cpp +++ b/Source/StevesUEHelpers/Private/StevesBPL.cpp @@ -15,3 +15,8 @@ void UStevesBPL::InsertChildWidgetAt(UPanelWidget* Parent, UWidget* Child, int A { StevesUiHelpers::InsertChildWidgetAt(Parent, Child, AtIndex); } + +FStevesBalancedRandomStream UStevesBPL::MakeBalancedRandomStream(int64 Seed) +{ + return FStevesBalancedRandomStream(Seed); +} diff --git a/Source/StevesUEHelpers/Public/StevesBPL.h b/Source/StevesUEHelpers/Public/StevesBPL.h index b6b71f5..22eecdc 100644 --- a/Source/StevesUEHelpers/Public/StevesBPL.h +++ b/Source/StevesUEHelpers/Public/StevesBPL.h @@ -3,6 +3,7 @@ #pragma once #include "CoreMinimal.h" +#include "StevesBalancedRandomStream.h" #include "StevesMathHelpers.h" #include "StevesBPL.generated.h" @@ -53,5 +54,23 @@ public: */ UFUNCTION(BlueprintCallable, Category="StevesUEHelpers|UI") static void InsertChildWidgetAt(UPanelWidget* Parent, UWidget* Child, int AtIndex = 0); - + + UFUNCTION(BlueprintPure, Category="StevesUEHelpers|Random", meta=(NativeMakeFunc)) + static FStevesBalancedRandomStream MakeBalancedRandomStream(int64 Seed); + + UFUNCTION(BlueprintCallable, Category="StevesUEHelpers|Random") + static float BalancedRandom(const FStevesBalancedRandomStream& Stream) { return Stream.Rand(); } + + UFUNCTION(BlueprintCallable, Category="StevesUEHelpers|Random") + static FVector2D BalancedRandom2D(const FStevesBalancedRandomStream& Stream) { return Stream.Rand2D(); } + + UFUNCTION(BlueprintCallable, Category="StevesUEHelpers|Random") + static FVector BalancedRandom3D(const FStevesBalancedRandomStream& Stream) { return Stream.Rand3D(); } + + UFUNCTION(BlueprintCallable, Category="StevesUEHelpers|Random") + static FVector BalancedRandomVector(const FStevesBalancedRandomStream& Stream) { return Stream.RandUnitVector(); } + + UFUNCTION(BlueprintCallable, Category="StevesUEHelpers|Random") + static FVector BalancedRandomPointInBox(const FStevesBalancedRandomStream& Stream, const FVector& Min, const FVector& Max) { return Stream.RandPointInBox(FBox(Min, Max)); } + }; diff --git a/Source/StevesUEHelpers/Public/StevesBalancedRandomStream.h b/Source/StevesUEHelpers/Public/StevesBalancedRandomStream.h new file mode 100644 index 0000000..2e8265a --- /dev/null +++ b/Source/StevesUEHelpers/Public/StevesBalancedRandomStream.h @@ -0,0 +1,165 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Math/Halton.h" +#include "StevesBalancedRandomStream.generated.h" + +/// "Balanced" random stream, using the Halton Sequence +/// This is deterministic and more uniform in appearance than a general random stream (although not perfectly uniform) +USTRUCT(BlueprintType, meta=(HasNativeMake="StevesUEHelpers.StevesBPL.MakeBalancedRandomStream")) +struct STEVESUEHELPERS_API FStevesBalancedRandomStream +{ + GENERATED_BODY() + +protected: + int32 InitialSeed = 0; + mutable uint32 Seed = 0; + +public: + + FStevesBalancedRandomStream() + : InitialSeed(0) + , Seed(0) + { } + + /** + * Creates and initializes a new random stream from the specified seed value. + * + * @param InSeed The seed value. + */ + FStevesBalancedRandomStream( int32 InSeed ) + { + Initialize(InSeed); + } + + /** + * Creates and initializes a new random stream from the specified name. + * + * @note If NAME_None is provided, the stream will be seeded using the current time. + * @param InName The name value from which the stream will be initialized. + */ + FStevesBalancedRandomStream( FName InName ) + { + Initialize(InName); + } + + /** + * Initializes this random stream with the specified seed value. + * + * @param InSeed The seed value. + */ + void Initialize( int32 InSeed ) + { + InitialSeed = InSeed; + Seed = uint32(InSeed); + } + + /** + * Initializes this random stream using the specified name. + * + * @note If NAME_None is provided, the stream will be seeded using the current time. + * @param InName The name value from which the stream will be initialized. + */ + void Initialize( FName InName ) + { + if (InName != NAME_None) + { + InitialSeed = GetTypeHash(InName.ToString()); + } + else + { + InitialSeed = FPlatformTime::Cycles(); + } + + Seed = uint32(InitialSeed); + } + + /** + * Resets this random stream to the initial seed value. + */ + void Reset() const + { + Seed = uint32(InitialSeed); + } + + int32 GetInitialSeed() const + { + return InitialSeed; + } + + /** + * Generates a new random seed. + */ + void GenerateNewSeed() + { + Initialize(FMath::Rand()); + } + + + /// Return a value between 0..1, inclusive + float Rand() const + { + return Halton(Seed++, 2); + } + + /// Return a 2D value with each element between 0..1, inclusive + /// Use this rather than calling Rand() twice to ensure balanced distribution + FVector2D Rand2D() const + { + const FVector2D Result( + Halton(Seed, 2), + Halton(Seed, 3)); + ++Seed; + return Result; + } + + /// Return a 3D value with each element between 0..1, inclusive + /// Use this rather than calling Rand() twice to ensure balanced distribution + FVector Rand3D() const + { + const FVector Result( + Halton(Seed, 2), + Halton(Seed, 3), + Halton(Seed, 5)); + ++Seed; + return Result; + } + + /** + * Returns a random vector of unit size. + * + * @return Random unit vector. + */ + FVector RandUnitVector() const + { + const FVector2D PitchYaw = Rand2D(); + return FRotator(PitchYaw.X, PitchYaw.Y, 0).RotateVector(FVector::UpVector); + } + + FORCEINLINE FVector RandPointInBox(const FBox& Box) const + { + const FVector R3 = Rand3D(); + return FVector(FMath::Lerp(Box.Min.X, Box.Max.X, R3.X), + FMath::Lerp(Box.Min.Y, Box.Max.Y, R3.Y), + FMath::Lerp(Box.Min.Z, Box.Max.Z, R3.Z)); + } + + /** + * Gets the current seed. + * + * @return Current seed. + */ + int32 GetCurrentSeed() const + { + return int32(InitialSeed); + } + + + FString ToString() const + { + return FString::Printf(TEXT("FStevesBalancedRandomStream(InitialSeed=%i, Seed=%u)"), InitialSeed, InitialSeed); + } + + +};