Keeping a Third Person Camera at a fixed distance - DirectX 11

Started by
11 comments, last by Gnollrunner 5 years, 3 months ago
On 12/21/2018 at 11:02 PM, Gnollrunner said:

Well I tend to be too lazy to analyze code ? but ..... here's some code from one of my simpler object follow classes that I use with DirectX 11 for cameras. I'm not gong to claim it's the most efferent or anything but it works OK.  I should say I used my own libraries so the code might look a little alien but I guess it should be easy enough to understand.

First an explanation ......  m_clTarget in the second code block is of type CDLPositionRecord which is a class that holds the position, and three vectors that point forward, up and right in relation to whatever character (or any object really) that you want to follow. You really only need two of the vectors to define an object's orientation, but having all three is sometimes convenient. Here's what the class and it's parent look like. You can ignore iFrame. Note that CDLPositionStruct is the same as CDLPositionRecord for all practical purposes.

 



struct CDLPositionStruct
{
   CDLPositionStruct() {}
   CDLPositionStruct(EDLPositionDefault eDummy) :
      clPosition(0.0,0.0,0.0),
      clFace(0.0,0.0,1.0),
      clUp(0.0,1.0,0.0),
      clRight(1.0,0.0,0.0)
   {}

   CDLDblPoint  clPosition;
   CDLDblVector clFace;
   CDLDblVector clUp;
   CDLDblVector clRight;
};

/**
 *****************************************************************
 **
 *****************************************************************
 **/

struct CDLPositionRecord : public CDLPositionStruct
{
   IDLFrame iFrame;
};


Here is the code that actually controls the camera:
 



void CDLReferenceFollow::Update(const CDLRenderData *pRenderData)
{
      // Are we tracking?
      if (m_bTracking) {
         CDLObject *pTargetParent = this->m_pTarget()->GetParent()->GetParent();
         // Target's reference and this reference should have the same parent
         assert(m_pParent == pTargetParent);
         // If our target moved or we changed elevation or azimuth we must recalculate
         m_bTargetMoved = m_pTarget()->GetFocus(m_clTarget);
      }

      m_mxChange.lock();

      if (m_bChanged || m_bTargetMoved ) {

         CDLPositionStruct clTemp;

         CDLDblQuaternion clARot(this->m_clTarget.clUp,this->m_fCAzimuth);
         clTemp.clFace = clARot.Rotate(this->m_clTarget.clFace);

         clTemp.clRight.CrossProduct(clTemp.clFace, this->m_clTarget.clUp);

         CDLDblQuaternion clERot( clTemp.clRight,this->m_fCElevation);
         clTemp.clFace = clERot.Rotate(clTemp.clFace);

         // Normalize for good measure
         clTemp.clFace.Normalize();

         clTemp.clUp.CrossProduct(clTemp.clFace, clTemp.clRight);

         clTemp.clPosition = m_clTarget.clPosition - (clTemp.clFace * this->m_fCDistance);

         // Thread safe store
         m_clPos.store(clTemp);

         m_bChanged = m_bTargetMoved = false;
      }
       m_mxChange.unlock();

}

At the top we are basically getting the position of our character (if tracking is turned on). You can ignore most of this, but just know that m_clTraget gets the character position and orientation data. We also get m_bTargetMoved which is really an optimization that tells us if the character data changed from the last call but it's not so important to implement this.

m_bChanged is another optimization which tells us if either the Azimuth, Elevation or Distance from the target character has changed.  These values are relative to the target character position and orientation. So for instance you could just set them to follow directly behind the character at some fixed distance, or you could have some elevation or even view the character from the side as it moves. Whatever the case the camera will always point to the clPosition field of m_clTarget. In general you want to make that its head.

We next use all our data to calculate the position and orientation of our camera and put it directly into clTemp, which is then copied to m_clPos, which is just an atomic version of a CDLPositionStruct for thread safety.

Finally we clear our m_bChanged and m_bTargetMoved and unlock our mutex.

A couple more things.  My stuff has to work on a spherical world so the character can be at any orientation. You could simplify this a lot if you assume a flat world with the character always pointing up, but then again you can use this for spacecraft too so there are some advantages. You can also change m_fCAzimuth, m_fCElevation and m_fCDistance freely at any time, so it's easy to attach those to the mouse and/or keyboard.

Hope this helps somewhat.. Good luck!

Edit: one more point. For this kind of camera you will probably eventually have to add collision detection which is not yet in my code.  One way to do this is to simply walk back from the character along the camera facing vector and then set the distance to just before the first collision. It's not so bad once you have your world data in an octree or some other collision friendly data structure, but it's something to consider.

Would you be able to go into more detail about what exactly you're doing here?


CDLDblQuaternion clARot(this->m_clTarget.clUp,this->m_fCAzimuth);
         clTemp.clFace = clARot.Rotate(this->m_clTarget.clFace);

         clTemp.clRight.CrossProduct(clTemp.clFace, this->m_clTarget.clUp);

         CDLDblQuaternion clERot( clTemp.clRight,this->m_fCElevation);
         clTemp.clFace = clERot.Rotate(clTemp.clFace);

I've not done Quaternion's before so it's a little confusing

Advertisement
3 hours ago, Zakwayda said:

In your case (based on your original post), the angle would probably just be a variable that you'd modify in response to mouse movements.

What about fixed_dst? I've currently got this:


void PlayerCamera::RotateAroundPlayer()
{
	XMVECTOR cameraEye = XMLoadFloat4(&Eye);
	XMVECTOR cameraUp = XMLoadFloat4(&Up);
	XMVECTOR atPlayerPos = XMLoadFloat4(&this->playerPos);

	XMFLOAT4 distance = XMFLOAT4(0.0f, 20.0f, -15.0f, 0.0f);
	XMVECTOR distanceAway = XMLoadFloat4(&Eye);

	XMVECTOR playerDirection = XMVector3Normalize(atPlayerPos + cameraEye);
	XMVECTOR cameraRight = XMVector3Cross(playerDirection, XMVector3Normalize(cameraUp));

	XMFLOAT4 cameraDistance;
	XMStoreFloat4(&cameraDistance, distanceAway * cameraRight);

	Eye.x = playerPos.x + (cameraDistance.x * std::sin(camYaw));
	Eye.z = playerPos.z + (cameraDistance.z * std::cos(camYaw));
}

It SORT of works... but still doesn't give the desired effect

Stupid_Images.gif

I should also mention that the camYaw is the mouse's X value multiplied by a scaler which is meant to be how fast the camera rotates


camYaw += input->GetMouseInput().X * 0.05f;
15 hours ago, Codelyy said:

Would you be able to go into more detail about what exactly you're doing here?



CDLDblQuaternion clARot(this->m_clTarget.clUp,this->m_fCAzimuth         clTemp.clFace = clARot.Rotate(this->m_clTarget.clFace);

         clTemp.clRight.CrossProduct(clTemp.clFace, this->m_clTarget.clUp);

         CDLDblQuaternion clERot( clTemp.clRight,this->m_fCElevation);
         clTemp.clFace = clERot.Rotate(clTemp.clFace);

I've not done Quaternion's before so it's a little confusing

So basically you can think of a Quaternion  as a four float array that encodes an axis and an angle to rotate around that axis. So clARot ends consisting of the up vector of the target object you are following (i.e. the axis) and the angle is the azimuth from the direction our target is facing. 

Next we use the Quaternion we just made to rotate the  facing vector of our target, to get the desired azimuth vector of our camera. By taking the cross product of our camera facing azimuth  vector (we just calculated) and our target up vector again, we get the right vector of our camera. we use this new value to create our next Quaternion which we then use to calculate the final camera facing vector by including in the elevation. Finally we do another cross product of our camera facing vector and our camera right vector, to get our camera up vector.  Note that all these vectors we are calculating are unit vectors. So we can just multiply our final camera facing vector by our desired distance and subtract that from our target (character) position to get our final camera position.  so we have all the relevant vectors for our camera plus it's position which is really all we need.

We can easily build a Look At matrix from that. if you leave the input vectors alone (this->fCAzimuth, this->fCElevation and this->fCDistance) the camera will always follow the target in the same position relative to the target as it yaws, rolls, or changes pitch. As @Irusan, son of Arusan stated you can also put springs on the azimuth, elevation and distance changes to give you kind of a smooth lag effect when you target is moving. I have another version of this camera controller that does exactly this, but it's a bit more complex and you of course need to feed it the game timer for it to work.

Also if you didn't get my sarcasm, there is no way the sin/cos code posted earlier is ever going to work in it's current form. So you should probably just ignore it.

 

This topic is closed to new replies.

Advertisement