How to find the neck pivot point in VR

So, I spent far longer on this than I should have, but I’ve finally solved this and wanted to share with anyone else who runs into this issue.

Issue:
You want to know exactly where the player character is in world space. You query the HMD position and you get some value. Great! You set some actor to that position, such as the players head. However, what happens to the players position if they look down, up, or roll their head side to side? Try it in your chair. Your torso never moves, but your head does. And the HMD is attached to the head. So, as the player moves their head around, the tracked position changes slightly as well. If you’re placing things relative to the body position and you’re assuming that body position = head position, you’re going to be in for a bit of pain. Through lots of experimentation, I discovered the left and right limits of rolling my neck and the pitch limits as well. Here are my personal results, though results may vary by person:
[table=“width: 800, class: grid, align: left”]

ID
X
Y
Z
Roll
Pitch
Yaw
Note


0
0
0
0
0
0
0
center


1
4.8
0
0
0
-58.759
0
look down


2
-11.97
0
0
0
61.09
0
look up


3
0
11.15
0
40.869
0
0
roll right


4
0
-10.12
0
-31.629
0
0
roll left

These are preset offset values I came up with, but they can be recalibrated to a players settings if you want (make sure you sanitize the input values!).

So, I found that the head moves in arcs. The math was too messy to deal with head positions on an arc, so I decided to do the simplest solution I could imagine: The head should be treated sort of like a blend space, where the Y value is the pitch and the X value is the roll. At the extremes of both ends of the axes, I have the calibrated position offset of the head. ie, roll your head as far right as you can, and your total head displacement on the X axis is 11.15 units.

If you sample the HMD rotation, you can get the current pitch and roll of the players head. We know that this value will either be negative or positive, so if the pitch is positive, we’re looking up. Negative, we’re looking down. We know the extreme limits of the players physical capabilities, so we can do an inverse lerp against their extreme limit vs. their current value to get a ratio between 0->1.0;
We then take this ratio as the alpha value to lerp between the origin and the head displacement at that extreme. The key is to lerp between the head rest position and the edge, rather than lerping between extremes because the ratios are not necessarily linear and the lerp path needs to be a bit of an arc (you can look down much further than you can look up).

Using the notation from the table above, Here’s my headset offset pseudo-code:



FVector GetHeadOffset(FRotator HMDRotation)
{
float alphaPitch = 0;
float alphaRoll = 0;
FVector PitchOffset, RollOffset;
   if(HMDRotation.pitch < 0)
   {
     alphaPitch = InvLerp(0, CalibratedHeadRot[1].Pitch, HMDRotation.pitch);
     pitchOffset = FVector::Lerp(FVector(0), CalibratedHeadPos[1], alphaPitch);
   }
   else
   {
     alphaPitch = InvLerp(0, CalibratedHeadRot[2].Pitch, HMDRotation.roll);
     pitchOffset = FVector::Lerp(FVector(0), CalibratedHeadPos[2], alphaPitch);
   }

if(HMDRotation.roll< 0)
   {
     alphaRoll = InvLerp(0, CalibratedHeadRot[3].Roll, HMDRotation.roll);
     rollOffset = FVector::Lerp(FVector(0), CalibratedHeadPos[3], alphaRoll );
   }
   else
   {
     alphaRoll = InvLerp(0, CalibratedHeadRot[4].Roll, HMDRotation.pitch);
     rollOffset = FVector::Lerp(FVector(0), CalibratedHeadPos[4], alphaRoll );
   }

  return FVector::RotateVector(PitchOffset + RollOffset, WorldCameraYaw);
}


Now, we can have a better idea on where the players neck is actually located. Just get the HMD World position and subtract this offset from it to get the neck position. This is a much better position to use for the players body than purely just grabbing the HMD position in world space :slight_smile:

you are god .