## Learning from mistakes

When I first tried to translate raw mouse input into camera rotation in my FPS game I encountered a problem. When translating raw input into camera rotation I was either getting a rotation which was too coarse or a rotation which was too slow.

I then tried to do mouse acceleration as I was told that other games do that. I thought I solved the mouse issues, but I was wrong. Mouse acceleration improved it a bit, but I wasn’t satisfied with the results(Mouse Ballistics).

I tested other games and I found that an x distance on the mouse pad was translated to the exact same y distance on mouse rotation no matter what speed I moved the mouse. However, mouse movement was still smooth and sufficiently fast.

Raw mouse input has this exact attribute of linear correlation between physical movement and the raw values. But when I used the raw input values linearly, it didn’t give good results. So what was the difference?

From watching other games, I think the solution is simply that the mouse values are converted linearly into screen pixel movement rather than camera angular movement.

It make sense because on the desktop we move the mouse by pixel units rather than rotating a camera with angles. So translating raw input linearly to rotation angles was not good enough.

## From theory to code

How do we translate mouse movement into pixel movement in an FPS camera?

A simple FPS camera usually have an Up direction and can rotate around the Y axis(if the Y axis is the Up axis) and can have a pitch(around the X axis). What we will do is translate pixel movement into angle movement and then rotate the camera as usual.

Some code:

`float fh = 0.25f;`

float dy = y/fh;

float4 p = mProj.Inverted().Mul (float4(0, dy, 0, 1));

b+=atan2(p.y/p.w, mNearZ);

Let’s review the code.

This code is the pitch angle update(rotation around the X axis).

*fh *is a raw input normalization factor. It depends on the range of raw mouse values and it can be thought of as a sensitivity factor. In my case I have found 0.25 is a good value.

*y *is the raw input y mouse value.

*mProj *is the FPS camera’s projection matrix used to translate from 3D coordinates into the screen space.

*mNear *is the distance from the camera to the near clipping plane in the camera frustum.

As you can see, I multiply the inverse of the projection matrix with screen space coordinates which are the number of pixels to move translated into screen space.

Screen space is the normalized box which DirectX’s rasterizer use to draw pixels into the back buffer. In DirectX’s case it is a box of (-1, 1)x(-1, 1)x(0, 1). This is why I give the Z value of the coordinate 0 as I want the coordinate to be on the near plane.

What multiplying the screen space coordinate by the inverse of the projection matrix do, is to translate screen space coordinates into a 3D(homogenous) vector in world or view space.

We need to divide the vector by the 4th coordinate because we gave 1 in the screen space vector’s 4th coordinate which means we assume the normalizer is 1 when it actually should be z.

Now we have the distance in view\world space of our vector from the camera’s look axis.

In order to calculate the angle we just need to do arc tangent of the y coordinate divided by the z coordinate(which is the distance of the near plane from the camera).

For the sake of completion I also include the code for calculating the camera’s matrices from the rotations:

`float4x4 R1 = float4x4::RotateY (a, float3(0, 0, 0));`

float4x4 R2 = float4x4::RotateX (b, float3(0, 0, 0));

float4x4 R3 = float4x4::RotateZ (Roll, float3(0, 0, 0));

float3 Look(0, 0, 1);

float3 U = R3.MulDir(float3(0, 1, 0));

float3 L = R1.MulDir(R2.MulDir(Look));

float3 R = U.Cross(L);

U = L.Cross(R);

mRight = R;

mUp = U;

mLook = L;

## Conclusion

This seems to give a much nicer and smoother results than what I did previously. I also know that other games have correlation between physical movement and screen movement which means other games have achieved good mouse movement with linear movement.

I hope I found a good solution.