Files
ProjectEleri/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesRuntime/Private/Libraries/DirectiveUtilMathRandomFunctionLibrary.cpp

220 lines
5.7 KiB
C++

// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
#include "Libraries/DirectiveUtilMathFunctionLibrary.h"
namespace
{
float GetUsableRandomWeight(const float Weight)
{
return FMath::IsFinite(Weight) && Weight > 0.0f ? Weight : 0.0f;
}
FVector2D MakeRandomPointInAnnulus(const double InnerRadius, const double OuterRadius,
const double AngleSample, const double RadiusSample)
{
const double Angle = AngleSample * UE_TWO_PI;
const double Radius = FMath::Sqrt(FMath::Lerp(
InnerRadius * InnerRadius,
OuterRadius * OuterRadius,
RadiusSample));
return FVector2D(FMath::Cos(Angle) * Radius, FMath::Sin(Angle) * Radius);
}
template <typename RandomFractionFunction>
FVector MakeRandomPointInSphere(const double Radius, RandomFractionFunction&& RandomFraction)
{
FVector Point;
double SizeSquared;
do
{
const double X = static_cast<double>(RandomFraction()) * 2.0 - 1.0;
const double Y = static_cast<double>(RandomFraction()) * 2.0 - 1.0;
const double Z = static_cast<double>(RandomFraction()) * 2.0 - 1.0;
Point = FVector(X, Y, Z);
SizeSquared = Point.SizeSquared();
}
while (SizeSquared > 1.0);
return Point * Radius;
}
}
int32 UDirectiveUtilMathFunctionLibrary::GetRandomIndexFromWeights(const TArray<float>& Weights)
{
double Total = 0.0;
for (const float Weight : Weights)
{
Total += GetUsableRandomWeight(Weight);
}
if (Total <= 0.0)
{
return INDEX_NONE;
}
const double Roll = static_cast<double>(FMath::FRand()) * Total;
double Accumulated = 0.0;
int32 LastPositiveIndex = INDEX_NONE;
for (int32 Index = 0; Index < Weights.Num(); ++Index)
{
const float Weight = GetUsableRandomWeight(Weights[Index]);
if (Weight <= 0.0f)
{
continue;
}
LastPositiveIndex = Index;
Accumulated += Weight;
if (Roll < Accumulated)
{
return Index;
}
}
return LastPositiveIndex;
}
int32 UDirectiveUtilMathFunctionLibrary::GetRandomIndexFromWeightsFromStream(FRandomStream& Stream, const TArray<float>& Weights)
{
double Total = 0.0;
for (const float Weight : Weights)
{
Total += GetUsableRandomWeight(Weight);
}
if (Total <= 0.0)
{
return INDEX_NONE;
}
const double Roll = static_cast<double>(Stream.FRand()) * Total;
double Accumulated = 0.0;
int32 LastPositiveIndex = INDEX_NONE;
for (int32 Index = 0; Index < Weights.Num(); ++Index)
{
const float Weight = GetUsableRandomWeight(Weights[Index]);
if (Weight <= 0.0f)
{
continue;
}
LastPositiveIndex = Index;
Accumulated += Weight;
if (Roll < Accumulated)
{
return Index;
}
}
return LastPositiveIndex;
}
FVector2D UDirectiveUtilMathFunctionLibrary::RandomPointInCircle(const float Radius)
{
if (!FMath::IsFinite(Radius))
{
return FVector2D::ZeroVector;
}
const double AbsoluteRadius = FMath::Abs(static_cast<double>(Radius));
if (AbsoluteRadius == 0.0)
{
return FVector2D::ZeroVector;
}
const double AngleSample = FMath::FRand();
const double RadiusSample = FMath::FRand();
return MakeRandomPointInAnnulus(0.0, AbsoluteRadius, AngleSample, RadiusSample);
}
FVector2D UDirectiveUtilMathFunctionLibrary::RandomPointInCircleFromStream(FRandomStream& Stream, const float Radius)
{
if (!FMath::IsFinite(Radius))
{
return FVector2D::ZeroVector;
}
const double AbsoluteRadius = FMath::Abs(static_cast<double>(Radius));
if (AbsoluteRadius == 0.0)
{
return FVector2D::ZeroVector;
}
const double AngleSample = Stream.FRand();
const double RadiusSample = Stream.FRand();
return MakeRandomPointInAnnulus(0.0, AbsoluteRadius, AngleSample, RadiusSample);
}
FVector2D UDirectiveUtilMathFunctionLibrary::RandomPointInAnnulus(const float InnerRadius, const float OuterRadius)
{
if (!FMath::IsFinite(InnerRadius) || !FMath::IsFinite(OuterRadius))
{
return FVector2D::ZeroVector;
}
const double FirstRadius = FMath::Abs(static_cast<double>(InnerRadius));
const double SecondRadius = FMath::Abs(static_cast<double>(OuterRadius));
const double Inner = FMath::Min(FirstRadius, SecondRadius);
const double Outer = FMath::Max(FirstRadius, SecondRadius);
if (Outer == 0.0)
{
return FVector2D::ZeroVector;
}
const double AngleSample = FMath::FRand();
const double RadiusSample = FMath::FRand();
return MakeRandomPointInAnnulus(Inner, Outer, AngleSample, RadiusSample);
}
FVector2D UDirectiveUtilMathFunctionLibrary::RandomPointInAnnulusFromStream(FRandomStream& Stream,
const float InnerRadius, const float OuterRadius)
{
if (!FMath::IsFinite(InnerRadius) || !FMath::IsFinite(OuterRadius))
{
return FVector2D::ZeroVector;
}
const double FirstRadius = FMath::Abs(static_cast<double>(InnerRadius));
const double SecondRadius = FMath::Abs(static_cast<double>(OuterRadius));
const double Inner = FMath::Min(FirstRadius, SecondRadius);
const double Outer = FMath::Max(FirstRadius, SecondRadius);
if (Outer == 0.0)
{
return FVector2D::ZeroVector;
}
const double AngleSample = Stream.FRand();
const double RadiusSample = Stream.FRand();
return MakeRandomPointInAnnulus(Inner, Outer, AngleSample, RadiusSample);
}
FVector UDirectiveUtilMathFunctionLibrary::RandomPointInSphere(const float Radius)
{
if (!FMath::IsFinite(Radius))
{
return FVector::ZeroVector;
}
const double AbsoluteRadius = FMath::Abs(static_cast<double>(Radius));
if (AbsoluteRadius == 0.0)
{
return FVector::ZeroVector;
}
return MakeRandomPointInSphere(AbsoluteRadius, []
{
return FMath::FRand();
});
}
FVector UDirectiveUtilMathFunctionLibrary::RandomPointInSphereFromStream(FRandomStream& Stream, const float Radius)
{
if (!FMath::IsFinite(Radius))
{
return FVector::ZeroVector;
}
const double AbsoluteRadius = FMath::Abs(static_cast<double>(Radius));
if (AbsoluteRadius == 0.0)
{
return FVector::ZeroVector;
}
return MakeRandomPointInSphere(AbsoluteRadius, [&Stream]
{
return Stream.FRand();
});
}