Files
UnrealEngine/Engine/Source/Runtime/AnimGraphRuntime/Public/RBF/RBFSolver.h
2025-05-18 13:04:45 +08:00

290 lines
9.7 KiB
C

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Animation/AnimTypes.h"
#include "Containers/Array.h"
#include "Containers/EnumAsByte.h"
#include "CoreMinimal.h"
#include "Curves/RichCurve.h"
#include "Math/Quat.h"
#include "Math/Rotator.h"
#include "Math/UnrealMathSSE.h"
#include "Math/Vector.h"
#include "Templates/SharedPointer.h"
#include "UObject/ObjectMacros.h"
#include "RBFSolver.generated.h"
/** The solver type to use. The two solvers have different requirements. */
UENUM()
enum class ERBFSolverType : uint8
{
/** The additive solver sums up contributions from each target. It's faster
but may require more targets for a good coverage, and requires the
normalization step to be performed for smooth results.
*/
Additive,
/** The interpolative solver interpolates the values from each target based
on distance. As long as the input values are within the area bounded by
the targets, the interpolation is well-behaved and return weight values
within the 0% - 100% limit with no normalization required.
Interpolation also gives smoother results, with fewer targets, than additive
but at a higher computational cost.
*/
Interpolative
};
/** Function to use for each target falloff */
UENUM()
enum class ERBFFunctionType : uint8
{
Gaussian,
Exponential,
Linear,
Cubic,
Quintic,
/** Uses the setting of the parent container */
DefaultFunction
};
/** Method for determining distance from input to targets */
UENUM()
enum class ERBFDistanceMethod : uint8
{
/** Standard n-dimensional distance measure */
Euclidean,
/** Treat inputs as quaternion */
Quaternion,
/** Treat inputs as quaternion, and find distance between rotated TwistAxis direction */
SwingAngle,
/** Treat inputs as quaternion, and find distance between rotations around the TwistAxis direction */
TwistAngle,
/** Uses the setting of the parent container */
DefaultMethod
};
/** Method to normalize weights */
UENUM()
enum class ERBFNormalizeMethod : uint8
{
/** Only normalize above one */
OnlyNormalizeAboveOne,
/**
Always normalize.
Zero distribution weights stay zero.
*/
AlwaysNormalize,
/**
Normalize only within reference median. The median
is a cone with a minimum and maximum angle within
which the value will be interpolated between
non-normalized and normalized. This helps to define
the volume in which normalization is always required.
*/
NormalizeWithinMedian,
/**
Don't normalize at all. This should only be used with
the interpolative method, if it is known that all input
values will be within the area bounded by the targets.
*/
NoNormalization,
};
/** Struct storing a particular entry within the RBF */
USTRUCT()
struct FRBFEntry
{
GENERATED_BODY()
/** Set of values for this target, size must be TargetDimensions */
UPROPERTY(EditAnywhere, Category = RBFData)
TArray<float> Values;
/** Return a target as an rotator, assuming Values is a sequence of Euler entries. Index is which Euler to convert.*/
ANIMGRAPHRUNTIME_API FRotator AsRotator(int32 Index) const;
/** Return a target as a quaternion, assuming Values is a sequence of Euler entries. Index is which Euler to convert. */
ANIMGRAPHRUNTIME_API FQuat AsQuat(int32 Index) const;
ANIMGRAPHRUNTIME_API FVector AsVector(int32 Index) const;
/** Set this entry to 3 floats from supplied rotator */
ANIMGRAPHRUNTIME_API void AddFromRotator(const FRotator& InRot);
/** Set this entry to 3 floats from supplied vector */
ANIMGRAPHRUNTIME_API void AddFromVector(const FVector& InVector);
/** Return dimensionality of this target */
int32 GetDimensions() const
{
return Values.Num();
}
};
/** Data about a particular target in the RBF, including scaling factor */
USTRUCT()
struct FRBFTarget : public FRBFEntry
{
GENERATED_BODY()
/** How large the influence of this target. */
UPROPERTY(EditAnywhere, Category = RBFData)
float ScaleFactor;
/** Whether we want to apply an additional custom curve when activating this target.
Ignored if the solver type is Interpolative.
*/
UPROPERTY(EditAnywhere, Category = RBFData)
bool bApplyCustomCurve;
/** Custom curve to apply to activation of this target, if bApplyCustomCurve is true.
Ignored if the solver type is Interpolative. */
UPROPERTY(EditAnywhere, Category = RBFData)
FRichCurve CustomCurve;
/** Override the distance method used to calculate the distance from this target to
the input. Ignored if the solver type is Interpolative. */
UPROPERTY(EditAnywhere, Category = RBFData)
ERBFDistanceMethod DistanceMethod;
/** Override the falloff function used to smooth the distance from this target to
the input. Ignored if the solver type is Interpolative. */
UPROPERTY(EditAnywhere, Category = RBFData)
ERBFFunctionType FunctionType;
FRBFTarget()
: ScaleFactor(1.f)
, bApplyCustomCurve(false)
, DistanceMethod(ERBFDistanceMethod::DefaultMethod)
, FunctionType(ERBFFunctionType::DefaultFunction)
{}
};
/** Struct for storing RBF results - target index and corresponding weight */
struct FRBFOutputWeight
{
/** Index of target */
int32 TargetIndex;
/** Weight of target */
float TargetWeight;
FRBFOutputWeight(int32 InTargetIndex, float InTargetWeight)
: TargetIndex(InTargetIndex)
, TargetWeight(InTargetWeight)
{}
FRBFOutputWeight()
: TargetIndex(0)
, TargetWeight(0.f)
{}
};
/** Parameters used by RBF solver */
USTRUCT(BlueprintType)
struct FRBFParams
{
GENERATED_BODY()
/** How many dimensions input data has */
UPROPERTY()
int32 TargetDimensions;
/** Specifies the type of solver to use. The additive solver requires normalization, for the
most part, whereas the Interpolative solver is not as reliant on it. The interpolative
solver also has smoother blending, whereas the additive solver requires more targets but
has a more precise control over the influence of each target.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = RBFData)
ERBFSolverType SolverType;
/** Default radius for each target.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = RBFData, meta = (EditCondition = "!bAutomaticRadius"))
float Radius;
/* Automatically pick the radius based on the average distance between targets */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = RBFData)
bool bAutomaticRadius;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = RBFData)
ERBFFunctionType Function;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = RBFData)
ERBFDistanceMethod DistanceMethod;
/** Axis to use when DistanceMethod is SwingAngle */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = RBFData)
TEnumAsByte<EBoneAxis> TwistAxis;
/** Weight below which we shouldn't bother returning a contribution from a target */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = RBFData)
float WeightThreshold;
/** Method to use for normalizing the weight */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = RBFData)
ERBFNormalizeMethod NormalizeMethod;
/** Rotation or position of median (used for normalization) */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = RBFData, meta = (EditCondition = "NormalizeMethod == ERBFNormalizeMethod::NormalizeWithinMedian"))
FVector MedianReference;
/** Minimum distance used for median */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = RBFData, meta = (UIMin = "0", UIMax = "90", EditCondition = "NormalizeMethod == ERBFNormalizeMethod::NormalizeWithinMedian"))
float MedianMin;
/** Maximum distance used for median */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = RBFData, meta = (UIMin = "0", UIMax = "90", EditCondition = "NormalizeMethod == ERBFNormalizeMethod::NormalizeWithinMedian"))
float MedianMax;
ANIMGRAPHRUNTIME_API FRBFParams();
/** Util for returning unit direction vector for swing axis */
ANIMGRAPHRUNTIME_API FVector GetTwistAxisVector() const;
};
struct ANIMGRAPHRUNTIME_API FRBFSolverData;
/** Library of Radial Basis Function solver functions */
struct FRBFSolver
{
/** Given a list of targets, verify which ones are valid for solving the RBF setup. This is mostly about removing identical targets
which invalidates the interpolative solver. Returns true if all targets are valid. */
static ANIMGRAPHRUNTIME_API bool ValidateTargets(const FRBFParams& Params, const TArray<FRBFTarget>& Targets, TArray<int>& InvalidTargets);
/** Given a set of targets and new input entry, give list of activated targets with weights */
static ANIMGRAPHRUNTIME_API TSharedPtr<const FRBFSolverData> InitSolver(const FRBFParams& Params, const TArray<FRBFTarget>& Targets);
static ANIMGRAPHRUNTIME_API bool IsSolverDataValid(const FRBFSolverData& SolverData, const FRBFParams& Params, const TArray<FRBFTarget>& Targets);
/** Given a set of targets and new input entry, give list of activated targets with weights */
static ANIMGRAPHRUNTIME_API void Solve(const FRBFSolverData& SolverData, const FRBFParams& Params, const TArray<FRBFTarget>& Targets, const FRBFEntry& Input, TArray<FRBFOutputWeight>& OutputWeights);
/** Util to find distance to nearest neighbour target for each target */
static ANIMGRAPHRUNTIME_API bool FindTargetNeighbourDistances(const FRBFParams& Params, const TArray<FRBFTarget>& Targets, TArray<float>& NeighbourDists);
/** Util to find distance between two entries, using provided params */
static ANIMGRAPHRUNTIME_API float FindDistanceBetweenEntries(const FRBFEntry& A, const FRBFEntry& B, const FRBFParams& Params, ERBFDistanceMethod OverrideMethod = ERBFDistanceMethod::DefaultMethod);
/** Returns the radius for a given target */
static ANIMGRAPHRUNTIME_API float GetRadiusForTarget(const FRBFTarget& Target, const FRBFParams& Params);
/** Compute the optimal radius for the given targets. Returns the radius */
static ANIMGRAPHRUNTIME_API float GetOptimalRadiusForTargets(const FRBFParams& Params, const TArray<FRBFTarget>& Targets);
};