• News

• Industries

• Learning & Support

• Community

• Marketplace

# Upper body IK or at least the torso orientation?

Has someone smarter than me - and that’s most of you - figured out a way to do that?

Getting the orientation of the torso would make my life much easier, even if it’s not precise down the degree…

Yeah, but you’d need a working IK model for that first though.

I’m sorry if I’m being dense but I know very little about IK.

Yeah, I’ve got it working. I can get the orientation of the players torso regardless of what direction their head is facing. It’s precise to about a 5 degree margin of error. Is this what you’re looking for?

Yep, that would be great!

Here’s the code I came up with to get the players physical torso orientation, independent of HMD orientation. I decided to leave my debug code in there in case you want to enable it and see what’s going on visually.
I have a few variables defined within the class (such as FVector LastTorso and FRotator DesiredTorso). The comments should help you fill in the gaps on what’s going on.

``````

{
/*Observations:
1) The human head cannot yaw more than 90 degrees to the left or right.
2) If you hold your arms to your side, they cant yaw more than 90 degrees left or right.
3) A player can be looking over their shoulder. This doesn't change their torso orientation IRL.
4) If your motion controller pitch is above 90 degrees, you need to flip the yaw to get the forward direction
5) Arms can be behind the center axis of a player
*/
//#define DEBUG_ENABLED 1

//Base Heading is the current orientation of the actor.
FVector BHForward = GetActorRotation().Vector();
BHForward.Z = 0;

FVector gazeYaw = FRotator(0, GazeYaw(), 0).RotateVector(FVector(1, 0, 0));
FVector HipPosition = GetPlayerHips();

#ifdef DEBUG_ENABLED

FVector DebugOffset = FVector(10, 0, -60);
{
FRotator DbgYaw = Gaze();
DbgYaw.Pitch = 0;
DbgYaw.Roll = 0;
DebugOffset = DbgYaw.RotateVector(DebugOffset);
}

//draw the gaze yaw
DrawDebugLine(GetWorld(), GetActorLocation() + DebugOffset, GetActorLocation() + (gazeYaw * 200) + DebugOffset, FColor::Green, false, 0, 0, .5f);

FVector GazeVec2 = GazeVec(200);
DrawDebugLine(GetWorld(), GetActorLocation(), GetActorLocation() + GazeVec2, FColor::Blue, false, 0, 0, .5f);

DrawDebugSphere(GetWorld(), HipPosition, 20, 12, FColor::Green);

#endif // DEBUG_ENABLED

if (UsingMotionControllers())
{
//if we're not tracking both hands, we can't get a hand midpoint!
if (LeftMC->IsTracked() && RightMC->IsTracked())
{
FVector LMC = GetMCPos(true);
FVector RMC = GetMCPos(false);
//We draw a line from one hand to the other and then divide it in half to get the midpoint on that line.
FVector HandMidPos = (LMC + RMC) / 2.0f;

//This is the line from the hips to the hand midpoint position
BHForward = HipPosition - HandMidPos;
BHForward.Z = 0;
BHForward.Normalize();

//the hands are behind the actor center point
if (FVector::DotProduct(BHForward, gazeYaw) < 0)
{
BHForward *= -1;
}

#ifdef DEBUG_ENABLED
DrawDebugSphere(GetWorld(), LMC, 12, 12, FColor::Blue);
DrawDebugSphere(GetWorld(), RMC, 12, 12, FColor::Red);

//draw lines connecting hands together
DrawDebugLine(GetWorld(), LMC, HandMidPos, FColor::Blue, false, 0, 0, .5f);
DrawDebugLine(GetWorld(), RMC, HandMidPos, FColor::Red, false, 0, 0, .5f);

//draw line from hip to hand
DrawDebugLine(GetWorld(), HipPosition, LMC, FColor::Blue, false, 0, 0, .5f);
DrawDebugLine(GetWorld(), HipPosition, RMC, FColor::Red, false, 0, 0, .5f);

//draw the base heading
DrawDebugLine(GetWorld(), HipPosition, HipPosition + (BHForward * 200), FColor::Purple, false, 0, 0, .5f);
#endif // DEBUG_ENABLED
}
}

//get the forward facing vector for the motion controller. Note that if you flip the controller upside down, we have to flip the forward vector.
FVector LMCForward = FVector();
if (LeftMC != NULL && LeftMC->IsTracked())		//if we lost tracking, we ignore this hand until it comes back
{
float LMCPitch = LeftMC->GetComponentRotation().Pitch;
if (!(LMCPitch > 85 || LMCPitch < -85))	//could use demorgans theorem here...
{
LMCForward = LeftMC->GetForwardVector();
//if (FVector::DotProduct(LMCForward, gazeYaw) < 0) LMCForward *= -1;

LMCForward.Z = 0;		//remove pitch
LMCForward.Normalize();
}
}

FVector RMCForward = FVector();
if (RightMC != NULL && RightMC->IsTracked())
{
float RMCPitch = RightMC->GetComponentRotation().Pitch;
if (!(RMCPitch > 85 || RMCPitch < -85))
{
RMCForward = RightMC->GetForwardVector();
//if (FVector::DotProduct(RMCForward, gazeYaw) < 0) RMCForward *= -1;

RMCForward.Z = 0;		//remove pitch
RMCForward.Normalize();
}
}

FVector HandSum = RMCForward + LMCForward;

#ifdef DEBUG_ENABLED
{
FVector LMC = GetMCPos(true);
FVector RMC = GetMCPos(false);
//draw lines connecting hands together
DrawDebugLine(GetWorld(), LMC, LMC + (LMCForward * 50), FColor::Blue, false, 0, 0, .5f);
DrawDebugLine(GetWorld(), RMC, RMC + (RMCForward * 50), FColor::Red, false, 0, 0, .5f);

//FVector HandSum = RMCForward * 50 + LMCForward * 50;

DrawDebugLine(GetWorld(), HipPosition, HipPosition + (LMCForward * 50), FColor::Cyan, false, 0, 0, .5f);
DrawDebugLine(GetWorld(), HipPosition + (LMCForward * 50), HipPosition + (RMCForward * 50) + (LMCForward * 50), FColor::Magenta, false, 0, 0, .5f);

DrawDebugLine(GetWorld(), HipPosition, HipPosition + HandSum, FColor::White, false, 0, 0, .5f);
}
#endif // DEBUG_ENABLED

FVector TorsoVec = gazeYaw;
if (FMath::Abs(UGameLib::GetTurnAngle(gazeYaw, HandSum)) > TorsoGazeThreshold)
{
HandSum.Normalize();
FVector HandVec = HandSum + BHForward;
HandVec.Normalize();

//use the hands for torso direction instead!
TorsoVec = HandVec;
}
//FVector TorsoVec = RMCForward + LMCForward + BHForward + Gaze + LastTorso;
//FVector TorsoVec = BHForward * 3 + Gaze;
//FVector TorsoVec = BHForward * 0.1f + gazeYaw + RMCForward * 0.1f + LMCForward * 0.1f + LastTorso * 5.f;
//TorsoVec.Z = 0;			//remove any pitch
TorsoVec.Normalize();

//run a sanity check against the gaze yaw vector: We know it can't be more than 90 degrees due to human physical constraints
if (FVector::DotProduct(gazeYaw, TorsoVec) < 0)
{
//houston, we got a problem... either the player became an owl or our math is wrong

//let's just take the vector average between the gaze vector and the last good torso vector
TorsoVec = gazeYaw + LastTorso;
TorsoVec.Z = 0;
TorsoVec.Normalize();
}

LastTorso = TorsoVec;

TorsoDesired = TorsoVec.Rotation();		//verify: should only have yaw here

//this is a signed value between -180 and 180
//above 0: turn left
//below 0: turn right
float RemainingTurn = FMath::FindDeltaAngleDegrees(TorsoCurrent.Yaw, TorsoDesired.Yaw);
#ifdef DEBUG_ENABLED
DebugText->SetText(FString::Printf(TEXT("DesiredYaw: %f
CurYaw: %f
AngleDelta: %f"), TorsoDesired.Yaw, TorsoCurrent.Yaw, RemainingTurn));
#endif

float VerySmallTurn = TorsoRotationSpeed * dt;		//2 degrees

if (FMath::Abs(RemainingTurn) < VerySmallTurn)
{
//remaining turn is less than our turn per frame, so we just snap to the desired angle.
TorsoCurrent.Yaw = TorsoDesired.Yaw;
}
else
{
if(RemainingTurn > 0)
{
TorsoCurrent.Yaw += VerySmallTurn;
}
else
{
TorsoCurrent.Yaw -= VerySmallTurn;
}
}

#ifdef DEBUG_ENABLED
{
FVector tmp = TorsoCurrent.RotateVector(FVector(100, 0, 0));
DrawDebugLine(GetWorld(), GetActorLocation(), GetActorLocation() + tmp, FColor::Yellow, false, 0, 0, .5f);
}
#endif // DEBUG_ENABLED
}

``````

Thanks Slayemin, I’m working with Blueprints right now but I’ll either transpose it or mix it up if it’s possible.

I’m not very familiar with c++. Too many years of Java and C# have soften me up. :o

If you’ve got any programming experience, I strongly encourage you to at least create a hybrid project with C++ and blueprints. By writing a single sentence of code, I can do something equivalent to 3-4 blueprint nodes and never worry about wire management.

Slayemin, would it be possible for you to help me include this into my own project?
I do have some C++ experience although, I find C++ with Unreal Engine a bit tedious. It keeps giving false errors and such.