Files
2025-05-18 13:04:45 +08:00

189 lines
6.6 KiB
C

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "RigVMCore/RigVMFunction.h"
#include "Units/Highlevel/RigUnit_HighlevelBase.h"
#include "Core/PBIKSolver.h"
#include "Core/PBIKDebug.h"
#include "PBIK_Shared.h"
#include "RigVMCore/RigVMMemoryStorage.h"
#include "RigUnit_PBIK.generated.h"
#define UE_API PBIK_API
struct FControlRigExecuteContext;
using PBIK::FDebugLine;
USTRUCT(BlueprintType)
struct FPBIKDebug
{
GENERATED_BODY()
/** The scale of the debug drawing. */
UPROPERTY(EditAnywhere, Category = Debug)
float DrawScale = 1.0f;
/** If true, turns on debug drawing for the node. */
UPROPERTY(EditAnywhere, Category = Debug)
bool bDrawDebug = false;
void Draw(FRigVMDrawInterface* DrawInterface, FPBIKSolver* Solver) const
{
if (!(DrawInterface && Solver && bDrawDebug))
{
return;
}
const FLinearColor Bright = FLinearColor(0.f, 1.f, 1.f, 1.f);
DrawInterface->DrawBox(FTransform::Identity, FTransform(FQuat::Identity, FVector(0, 0, 0), FVector(1.f, 1.f, 1.f) * DrawScale * 0.1f), Bright);
TArray<FDebugLine> BodyLines;
Solver->GetDebugDraw()->GetDebugLinesForBodies(BodyLines);
const FLinearColor BodyColor = FLinearColor(0.1f, 0.1f, 1.f, 1.f);
for (FDebugLine Line : BodyLines)
{
DrawInterface->DrawLine(FTransform::Identity, Line.A, Line.B, BodyColor);
}
}
};
USTRUCT(BlueprintType)
struct FPBIKEffector
{
GENERATED_BODY()
FPBIKEffector() :
Bone(NAME_None),
PositionAlpha(1.0f),
RotationAlpha(1.0f),
StrengthAlpha(1.0f),
ChainDepth(0),
PullChainAlpha(1.0f),
PinRotation(1.0f){}
/** The bone that this effector will pull on. */
UPROPERTY(EditAnywhere, Category="Effector", meta = (CustomWidget = "BoneName"))
FName Bone;
/** The target location and rotation for this effector. The solver will try to get the specified bone to reach this location.*/
UPROPERTY(EditAnywhere, Category="Effector")
FTransform Transform;
/** Range 0-1, default is 1. Blend between the input bone position (0.0) and the current effector position (1.0).*/
UPROPERTY(EditAnywhere, Category = "Goal Settings", meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0"))
float PositionAlpha = 1.0f;
/** Range 0-1, default is 1. Blend between the input bone rotation (0.0) and the current effector rotation (1.0).*/
UPROPERTY(EditAnywhere, Category = "Goal Settings", meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0"))
float RotationAlpha = 1.0f;
/** Range 0-1 (default is 1.0). The strength of the effector when pulling the bone towards it's target location.
* At 0.0, the effector does not pull at all, but the bones between the effector and the root will still slightly resist motion from other effectors.
* This can thus act as a "stabilizer" of sorts for parts of the body that you do not want to behave in a pure FK fashion.
*/
UPROPERTY(EditAnywhere, Category="Effector", meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0"))
float StrengthAlpha = 1.0f;
/** Range 0-inf (default is 0). Explicitly set the number of bones up the hierarchy to consider part of this effector's 'chain'.
* The "chain" of bones is used to apply Preferred Angles, Pull Chain Alpha and "Sub Solve" iterations.
* If left at 0, the solver will attempt to determine the root of the chain by searching up the hierarchy until it finds a branch or another effector, whichever it finds first.
*/
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Full Body IK Effector", meta = (ClampMin = "0", UIMin = "0"))
int32 ChainDepth = 0;
/** Range 0-1 (default is 1.0). When enabled (greater than 0.0), the solver internally partitions the skeleton into 'chains' which extend from the effector to the nearest fork in the skeleton.
*These chains are pre-rotated and translated, as a whole, towards the effector targets.
*This can improve the results for sparse bone chains, and significantly improve convergence on dense bone chains.
*But it may cause undesirable results in highly constrained bone chains (like robot arms).
*/
UPROPERTY(EditAnywhere, Category="Effector", meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0"))
float PullChainAlpha = 0.0f;
/** Range 0-1 (default is 1.0).
*Blends the effector bone rotation between the rotation of the effector transform (1.0) and the rotation of the input bone (0.0).*/
UPROPERTY(EditAnywhere, Category="Effector", meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0"))
float PinRotation = 1.0f;
};
USTRUCT()
struct FPBIKWorkData
{
GENERATED_BODY()
FPBIKWorkData() :
bNeedsInit(true),
HashInitializedWith(-1)
{
}
UPROPERTY(transient)
bool bNeedsInit;
UPROPERTY(transient)
uint32 HashInitializedWith;
UPROPERTY(transient)
TArray<int32> BoneSettingToSolverBoneIndex;
UPROPERTY(transient)
TArray<int32> SolverBoneToElementIndex;
UPROPERTY(transient)
FPBIKSolver Solver;
};
/*
* Based on a Position Based solver at core, this node can solve multi chains within a root using multi effectors
*/
USTRUCT(meta=(DisplayName="Full Body IK", Category="Hierarchy", Keywords="FBIK, Position Based, PBIK, IK, Full Body, Multi, Effector, N-Chain, FB, HIK, HumanIK", NodeColor="0 1 1"))
struct FRigUnit_PBIK : public FRigUnit_HighlevelBaseMutable
{
GENERATED_BODY()
RIGVM_METHOD()
UE_API virtual void Execute() override;
FRigUnit_PBIK() : Root(NAME_None) { }
/**This is usually the top-most skinned bone; often the "Pelvis" or "Hips", but can be set to any bone.
*Bones above the root will be ignored by the solver.
*Bones that are located *between* the Root and the effectors will be included in the solve.*/
UPROPERTY(meta = (Input, CustomWidget = "BoneName"))
FName Root;
/** An array of effectors. These specify target transforms for different parts of the skeleton. */
UPROPERTY(meta = (Input))
TArray<FPBIKEffector> Effectors;
UPROPERTY(transient)
TArray<int32> EffectorSolverIndices;
/** Per-bone settings to control the resulting pose. Includes limits and preferred angles. */
UPROPERTY(meta = (Input))
TArray<FPBIKBoneSetting> BoneSettings;
/** These bones will be excluded from the solver. They will not bend and will not contribute to the constraint set.
* Use the ExcludedBones array instead of setting Rotation Stiffness to very high values or Rotation Limits with zero range. */
UPROPERTY(meta = (Input, CustomWidget = "BoneName"))
TArray<FName> ExcludedBones;
/** Global solver settings. */
UPROPERTY(meta = (Input))
FPBIKSolverSettings Settings;
/** Debug drawing options. */
UPROPERTY(meta = (Input))
FPBIKDebug Debug;
/** Runtime-only data */
UPROPERTY(transient)
FPBIKWorkData WorkData;
};
#undef UE_API