// Copyright Epic Games, Inc. All Rights Reserved. #include "MuCOP/CustomizableObjectPopulationSamplers.h" #include "Curves/CurveLinearColor.h" #include "Math/RandomStream.h" //#include "MuCOP/CustomizableObjectPopulation.h" //#include "MuCOP/CustomizableObjectPopulationClass.h" namespace { TArray ComputeCumulativeWeights(const TArray& Weights) { TArray CumulativeWeights; const int32 NumWeights = Weights.Num(); CumulativeWeights.SetNum(NumWeights + 1); CumulativeWeights[0] = -1; int32 C = 0; for (int32 I = 0; I < NumWeights; ++I) { // A negative weight is not valid. Clamp to 0. C += FMath::Max(Weights[I], 0); CumulativeWeights[I + 1] = C; } return CumulativeWeights; } /* TArray GetPopulationClassWeightsFromPairs(const TArray& Pairs) { TArray Weights; Weights.SetNum(Pairs.Num()); for (int32 I = 0; I < Pairs.Num(); ++I) { Weights[I] = Pairs[I].ClassWeight; } return Weights; } TArray GetWeightsFormConstraintRanges(const TArray& Ranges) { TArray Weights; Weights.SetNum(Ranges.Num()); for (int32 I = 0; I < Ranges.Num(); ++I) { Weights[I] = Ranges[I].RangeWeight; } return Weights; } TArray> GetValuesFromConstraintRanges(const TArray& Ranges) { TArray> Values; Values.SetNum(Ranges.Num()); for (int32 I = 0; I < Ranges.Num(); ++I) { Values[I] = TTuple{Ranges[I].MinimumValue, Ranges[I].MaximumValue}; } return Values; } */ } //namespace namespace CustomizableObjectPopulation { // FDiscreteImportanceSampler FDiscreteImportanceSampler::FDiscreteImportanceSampler(const TArray& Weights) : CumulativeWeights(ComputeCumulativeWeights(Weights)) { } int32 FDiscreteImportanceSampler::Sample(FRandomStream& Rand) const { check(IsValidSampler()); const int32 NumOptions = CumulativeWeights.Num() - 1; int32 R = Rand.RandRange(0, CumulativeWeights[NumOptions] - 1); int32 I = NumOptions; while (CumulativeWeights[--I] > R); return I; } bool FDiscreteImportanceSampler::IsValidSampler() const { return CumulativeWeights.Num() > 1 && CumulativeWeights.Last() > 0; } FArchive& operator<<(FArchive& Ar, FDiscreteImportanceSampler& Sampler) { Ar << Sampler.CumulativeWeights; return Ar; } //////////////// // FOptionSampler FOptionSampler::FOptionSampler(const TArray& OptionWeights, const TArray& OptionNames) : FDiscreteImportanceSampler(OptionWeights) , OptionNames(OptionNames) { } const FString& FOptionSampler::GetOptionName(int32 OptionIndex) const { return OptionNames[OptionIndex]; } FArchive& operator<<(FArchive& Ar, FOptionSampler& Sampler) { Ar << static_cast(Sampler); Ar << Sampler.OptionNames; return Ar; } //////////////// // FBoolSampler FBoolSampler::FBoolSampler(int32 TrueWeight, int32 FalseWeight) : CumulativeValue(TrueWeight + FalseWeight) , TippingValue(TrueWeight) { } bool FBoolSampler::Sample(FRandomStream& Rand) const { const int32 R = Rand.RandRange(0, CumulativeValue - 1); return R < TippingValue; } bool FBoolSampler::IsValidSampler() const { return CumulativeValue > 0; } FArchive& operator<<(FArchive& Ar, FBoolSampler& Sampler) { Ar << Sampler.CumulativeValue; Ar << Sampler.TippingValue; return Ar; } //////////////// // FRangesSampler FRangesSampler::FRangesSampler(const TArray& RangesWeights, const TArray>& RangesValues) : DiscreteSampler(RangesWeights) , RangeValues(RangesValues) { } float FRangesSampler::Sample(FRandomStream& Rand) const { check(IsValidSampler()); int32 I = DiscreteSampler.Sample(Rand); return Rand.FRandRange(RangeValues[I].Get<0>(), RangeValues[I].Get<1>()); } bool FRangesSampler::IsValidSampler() const { return DiscreteSampler.IsValidSampler(); } FArchive& operator<<(FArchive& Ar, FRangesSampler& Sampler) { Ar << Sampler.DiscreteSampler; Ar << Sampler.RangeValues; return Ar; } //////////////// // FFloatUniformSampler FFloatUniformSampler::FFloatUniformSampler(const float InMin, const float InMax) : MinValue(InMin), MaxValue(InMax) { } float FFloatUniformSampler::Sample(FRandomStream& Rand) const { return Rand.FRandRange(MinValue, MaxValue); } bool FFloatUniformSampler::IsValidSampler() const { return MinValue < MaxValue; } FArchive& operator<<(FArchive& Ar, FFloatUniformSampler& Sampler) { Ar << Sampler.MinValue; Ar << Sampler.MaxValue; return Ar; } //////////////// // FCurveSampler FCurveSampler::FCurveSampler(const FRichCurve& InCurve, const int32 NumBins) { InCurve.CompressCurve(Curve); float MaxT; InCurve.GetTimeRange(MinT, MaxT); BinWidth = (MaxT - MinT) / float(NumBins); const float AreaSampleWidth = BinWidth / float(BinAreaSampleResolution); CumulativeBinWeights.SetNum(NumBins + 1); BinMaxHeights.SetNum(NumBins); CumulativeBinWeights[0] = -SMALL_NUMBER; float AbsoluteMax = 0.0f; // Compute cumulative bin weights. float W = 0.0f; for (int32 B = 0; B < NumBins; ++B) { float T = MinT + BinWidth * float(B); // Mid point Integration. float MaxHeight = 0.0f; float Default = 0.0f; for (int32 A = 0; A < BinAreaSampleResolution; ++A) { const float S = FMath::Max(Curve.Eval(T + AreaSampleWidth * 0.5f), 0.0f); if (S > MaxHeight) { MaxHeight = S; Default = T + AreaSampleWidth * 0.5f; } W += AreaSampleWidth * S; T += AreaSampleWidth; } CumulativeBinWeights[B + 1] = W; BinMaxHeights[B] = MaxHeight; if (MaxHeight > AbsoluteMax) { AbsoluteMax = MaxHeight; MaxDefault = Default; } } } float FCurveSampler::Sample(FRandomStream& Rand) const { check(IsValidSampler()); const int32 NumBins = CumulativeBinWeights.Num() - 1; const float CumulativeWeight = CumulativeBinWeights[NumBins]; float R = Rand.FRandRange(0.0f, CumulativeWeight); // Could use binary search if the number of bins is large. In our use case // a linear search is good enough (possibly faster for a small set of bins) int32 I = NumBins; while (CumulativeBinWeights[--I] > R); // Enter only if we are not under the worst case scenario const float BinArea = I == 0 ? CumulativeBinWeights[I] : CumulativeBinWeights[I] - CumulativeBinWeights[I - 1]; if (BinArea > 0.0001f) { const float BinMin = float(I) * BinWidth + MinT; const float BinMax = BinMin + BinWidth; const float BinHeight = BinMaxHeights[I]; for (int tries = 0; tries < 1 << 16; tries++) { const float S = Rand.FRandRange(BinMin, BinMax); const float T = Rand.FRand() * BinHeight; const float V = Curve.Eval(S); if (V > T) { return S; } } } // Return the most probable value found as default, to make sure we don't return a value that is not legal return MaxDefault; } bool FCurveSampler::IsValidSampler() const { return CumulativeBinWeights.Num() > 1 && CumulativeBinWeights.Last() > 0.0f; } FArchive& operator<<(FArchive& Ar, FCurveSampler& Sampler) { Ar << Sampler.BinWidth; Ar << Sampler.MinT; Ar << Sampler.MaxDefault; Ar << Sampler.BinMaxHeights; Ar << Sampler.CumulativeBinWeights; Ar << Sampler.Curve; return Ar; } //////////////// // FColorCurveUniformSampler FColorCurveUniformSampler::FColorCurveUniformSampler(UCurveLinearColor& InCurve, const float InMin, const float InMax) : MinValue(InMin), MaxValue(InMax) { for (uint8 i = 0; i < 4; ++i) { InCurve.FloatCurves[i].CompressCurve(ColorCurves[i]); } } FLinearColor FColorCurveUniformSampler::Sample(FRandomStream& Rand) const { const float randomPoint = Rand.FRandRange(MinValue, MaxValue); return FLinearColor(ColorCurves[0].Eval(randomPoint), ColorCurves[1].Eval(randomPoint), ColorCurves[2].Eval(randomPoint), ColorCurves[3].Eval(randomPoint)); } bool FColorCurveUniformSampler::IsValidSampler() const { return MinValue < MaxValue; } FArchive& operator<<(FArchive& Ar, FColorCurveUniformSampler& Sampler) { Ar << Sampler.MinValue; Ar << Sampler.MaxValue; Ar << Sampler.ColorCurves[0]; Ar << Sampler.ColorCurves[1]; Ar << Sampler.ColorCurves[2]; Ar << Sampler.ColorCurves[3]; return Ar; } //////////////// //FFloatSampler /* FFloatSampler::FFloatSampler(FCurveSampler&& InSampler) : Sampler( TInPlaceType{}, Forward(InSampler) ) { } FFloatSampler::FFloatSampler(FRangesSampler&& InSampler) : Sampler( TInPlaceType{}, Forward(InSampler) ) { } FFloatSampler::FFloatSampler(FFloatUniformSampler&& InSampler) : Sampler( TInPlaceType{}, Forward(InSampler) ) { } float FFloatSampler::Sample(FRandomStream& Rand) const { return Visit([&Rand](auto&& Arg) -> float { return Arg.Sample(Rand); }, Sampler); } bool FFloatSampler::IsValidSampler() const { return Visit([](auto&& Arg) -> bool { return Arg.IsValidSampler(); }, Sampler); } FArchive& operator<<(FArchive& Ar, FFloatSampler& Sampler) { using FloatSamplerType = FFloatSampler::FloatSamplerType; int32 TypeIndex = Sampler.Sampler.GetIndex(); Ar << TypeIndex; // TODO: Could this be done better? if (Ar.IsLoading()) { // Set Variant type from index if loading. switch (TypeIndex) { case FloatSamplerType::IndexOfType() : { Sampler.Sampler.Set(FCurveSampler()); break; } case FloatSamplerType::IndexOfType() : { Sampler.Sampler.Set(FRangesSampler()); break; } case FloatSamplerType::IndexOfType() : { Sampler.Sampler.Set(FFloatUniformSampler()); break; } default: { check(false); } } } Visit([&Ar](auto&& Arg) -> void { Ar << Arg; }, Sampler.Sampler); return Ar; } */ // FPopulationClassSampler FPopulationClassSampler::FPopulationClassSampler(const TArray& Weights) : FDiscreteImportanceSampler(Weights) { } FArchive& operator<<(FArchive& Ar, FPopulationClassSampler& Sampler) { Ar << static_cast(Sampler); return Ar; } FArchive& operator<<(FArchive& Ar, FConstraintIndex& SamplerIndexes) { Ar << SamplerIndexes.SamplerIndex; Ar << SamplerIndexes.SamplerType; return Ar; } FConstraintSampler::FConstraintSampler(const TArray& OptionWeights, const FString& ParameterName, const TArray& Samplers) : FDiscreteImportanceSampler(OptionWeights) , ParameterName(ParameterName) , Samplers(Samplers) { } const FConstraintIndex& FConstraintSampler::GetSamplerID(int32 SamplerIndex) const { return Samplers[SamplerIndex]; } FArchive& operator<<(FArchive& Ar, FConstraintSampler& Sampler) { Ar << static_cast(Sampler); Ar << Sampler.ParameterName; Ar << Sampler.Samplers; return Ar; } } // namespace CustomizableObjectPopulation ////////////////