Saturday, 18 August 2012

Understanding CryEngine 3 code: The camera

After having looked at how the player moves, I was interested in looking at how the camera's position is updated.

Using the information collected from my previous investigations, I followed a lead that seemed obvious. I was in for a surprise.

Camera Manager

Back when I was looking at the startup sequence, I discovered that when CGame gets constructed, there is a class called CCameraManager that is created at the same time. Here's the contents of CCameraManager's constructor:

CCameraManager::CCameraManager()
{
 //init cam overrides
 m_pCamOverrides = new CCameraOverrides();
 //init view
 m_pCameraView = new CCameraView(NULL);

 m_camNodes.reserve(32);
 AddNullCam();
 m_idPrev = 0;
 m_idActive = 0;
 SetActiveCameraId(0);
}

Camera Overrides take over part of the camera's properties to do things such as looking at a given entity or performing a zoom.

The rest of the constructor enlightens us on the way the camera system works: there's only one actual camera (the camera view), and several sets of camera properties (cam nodes), which get assigned to the camera view depending on which camera is active.

But that's the theory.

If you put breakpoints in the Update functions of the Camera Manager or the Camera View, you'll notice... nothing. They never get called, not even during a cutscene (which I think is very odd, I might have missed something there).

The only way I managed to get them to trigger is by going into third person mode (F1) and then setting the cvar cl_cam_orbit to 1 (I'll explain later how I discovered this).

So where is the camera updated then? To find out, we'll start by tracing something we know affects the camera: moving the mouse.

Player-controlled orientation and camera movement

As in the previous article, it starts in CPlayerInput. When you move the mouse, you Activate OnActionRotateYaw and/or OnActionRotatePitch.

If you remember the article on player movement, there is a CPlayerMovement class which is instantiated during CPlayer::PrePhysicsUpdate. Similarly, there is a CPlayerRotation class. Like its sibling, it operates in two steps: Process and Commit. And like with CPlayerMovement, I don't really see the point as the functions are called back to back.

Regardless, The Process function is where you branch your code depending on which state the player is in (in the default implementation, you can see specific cases for leaning, free falling, being parachuted, etc.).

In the normal situation, there is a function that clamps the limits of rotation (so we can't do a backflip when looking up).

Then, we learn that rotation actually deals with two quaternions:

  • m_BaseQuat: which represents the orientation of the player.
  • m_ViewQuat: which represents the orientation of the view (the camera).
The main difference is that m_ViewQuat takes the full information of rotation that has been defined in the previous stages, while m_BaseQuat (in a usual situation) only holds the rotation around the Z (vertical) axis, as it is assumed in an FPS that the character is (almost) always upright.

CPlayer::UpdateView


This is the function we were looking for. The following bit of code is particularly interesting:

if(g_pGameCVars->cl_cam_orbit != 0 && IsThirdPerson())
{
 CCameraView *pCamView = g_pGame->GetCameraManager()->GetCamView();
 pCamView->SetTarget(GetEntity());
 pCamView->Update(viewParams);
 // ---------8<-----------
}
else 
{
 CPlayerView playerView(*this,viewParams);
 playerView.Process(viewParams);
 playerView.Commit(*this,viewParams);

 if (!IsThirdPerson())
 {
  float animControlled = m_pAnimatedCharacter->FilterView(viewParams);

  // ---------8<-----------
 }
 // ---------8<-----------
}

This is pretty much the only place in the entire code base where the Camera Manager is solicited. But as you can see, this only happens under two specific conditions:

  • A console variable needs to be set.
  • The player must be in third person mode.

In this situation, we see that the code is getting the camera view and setting the camera's target to the entity representing the player. Then the camera view is updated. By updating, I actually mean that the camera view calculates the camera depending on the target and the type of camera. Eventually, the resulting matrix is fed back to the player class.

In any other case, it's a class called CPlayerView that does the job. Hidden behind the constructor, Process and Commit functions is a three step action: PreProcess, Process, and Post Process the view. What do these do?

ViewPreProcess

It's gathering lots of information from the view parameters and the player, and stores everything in an internal structure. And that's about it.

ViewProcess

As the comment above the function says, it takes the data gathered in PreProcess and does the actual job. Depending on what kind of view needs to be processed (First/Third person, spectator, in vehicle, etc.), different functions are called.

In ViewFirstPerson, it is pretty much just adding head bob and positioning the weapon model.

ViewPostProcess

It applies any post-process effects such has camera shake, and then apply the modifications to the player.


Back in CPlayer::UpdateView, you can see in the code snippet I've shown earlier that the character (i.e. the skeletal mesh) can have some control over the camera. However, that function lives in the infamous CryAction black box.

Eventually, the actual view matrix is modified, and UI Manager is told to update any HUD overlay.

In the end, it's going through a lot of hoops to get things done, but all the data seems accessible, so we should be able to do whatever we want with the camera. I still don't understand why the Camera Manager is almost never used, but I think I would probably start using it if I were to use a non-character centric camera system, or one that has several views.