Seeing sin and cos in a LookAt function makes me nervous. At some point, you may want to check out my Gimbal Lock video which discusses the problem with storing orientation as 3 separate values.
I’m also prejudiced against the mouse; so, I’m not the best person to ask about how to use a mouse in game programming. It’s a long story. But you’ll pretty much never see mouse code in my software except where I can get away with using a graphics tablet as if it were a mouse. That’s not to say you shouldn’t use a mouse. I think most users prefer it. I can’t imagine a PC game shipping without mouse support. Even I might break down and include it for a commercial game. Although personally I’m a big fan of the game pad for games.
Anyway, that aside, here’s the code from my elementary game engine which has a first person camera.
//=====================================================================================================================
// Game::Update()
//
// Purpose:
// To do everything that needs to be done in one frame except draw stuff to the screen.
//
// Input:
// float TimeDelta - Amount of time that has passed since the last frame occured in milliseconds.
//
// Output:
// None.
//
// Notes:
// This is where most of your game code will be. It gets called every frame to change anything that needs to be changed
// or done during that frame. It runs in a loop that needs to be called at least 30 times per second but there's nothing
// to control how often it gets called. Things would move at unpredictable rates if we did not use TimeDelta to take in to
// account the amount of time that has passed since the last frame.
//
// We start out by processing the keyboard and game controller input to change the camera's position and direction. You
// can also toggle full screen on and off.
//
// The camera movement this frame is stored as a 3D vector that we treat more like a 2D vector. The facing normal should
// point in the direction we want the camera to face. And as a normal should have a length of 1. Any movement during the
// frame is cumulative from the various controls. When you move it uses either the CameraFacingNormal or a normal rotated 90
// degrees away from the camera facing. It's basic vector addition to add the movement to the camera position.
//
// XMMatrixLookAtRH is used to create a view matrix to simulate the camera every frame. Generally, I would say it is a
// good idea to not continuously recreate the view matrix but it's easier then maintaining a view matrix between frames and
// this is really early in this tutorial series.
//
// Finally some very simple rigid animation is thrown in to show you not only how to do it, but that it can be done and how
// easy it is to do. Experiment by turning the rotations off and on and changing their directions and speed.
//
// The scene is lit with a simple Blinn-Phong shader that has "directional" lighting as opposed to point lights or
// spot lights. Directional lighting is nothing more than a direction that the light shines in. It is a normalized vector
// describing a direction and it has a color. That's all it is. Look at the shader for more detail. By rotating that direction
// the light source seems to orbit the scene similar to a day and night cycle except the light shines through solid objects.
//
//=====================================================================================================================
void Game::Update()
{
const float MaxTiltAngle = glm::radians(45.0);
const unsigned char* Buttons;
int JoyStick1Present = false;
int NumberOfJoyStickAxes = 0;
int NumberOfJoyStickButtons = 0;
const float* AxesArray = nullptr;
float LeftThumbStickY = 0.0f;
float LeftThumbStickX = 0.0f;
float Triggers = 0.0f; //XBox 360 controller triggers are a single axis for both triggers. Positive = Left. Negative = Right.
float RightThumbStickY = 0.0f;
float RightThumbStickX = 0.0f;
bool AButton = false;
bool BButton = false;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_ESCAPE && OperatingSystem.Keyboard.ActionPressed == GLFW_PRESS) OperatingSystem.ShutDown();
JoyStick1Present = glfwJoystickPresent(GLFW_JOYSTICK_1);
if (JoyStick1Present)
{
AxesArray = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &NumberOfJoyStickAxes);
Buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &NumberOfJoyStickButtons);
LeftThumbStickY = AxesArray[0];
LeftThumbStickX = AxesArray[1];
Triggers = AxesArray[2];
RightThumbStickY = AxesArray[3];
RightThumbStickX = AxesArray[4];
//Camera Controls with XBox 360 controller.
if (RightThumbStickX > 0.2 || RightThumbStickX < -0.2) View = glm::rotate(glm::mat4(), RightThumbStickX *0.06f, glm::vec3(0.0f, 1.0f, 0.0f)) * View;
if (LeftThumbStickX > 0.2 || LeftThumbStickX < -0.2) View = glm::translate(glm::mat4(), glm::vec3(0.0f, 0.0f, -LeftThumbStickX * 0.1f)) * View; //*0.1f to slow it down. Negative to flip the axis. -0.2 for deadzone.
if (RightThumbStickY > 0.2 || RightThumbStickY < -0.2) CameraTilt += 0.03 * RightThumbStickY;
if (LeftThumbStickY > 0.2 || LeftThumbStickY < -0.2) View = glm::translate(glm::mat4(), glm::vec3(-LeftThumbStickY * 0.1f, 0.0f, 0.0f)) * View;
if (Triggers > 0.2 || Triggers < -0.2) View = glm::translate(glm::mat4(), glm::vec3(0.0f, Triggers*0.1f, 0.0f)) * View;
if (Buttons[0] == '\x1') AButton = true;
if (Buttons[1] == '\x1') BButton = true;
if (Buttons[6] == '\x1') OperatingSystem.ShutDown();
}
//Camera Controls with keyboard.
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_W && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
View = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.05f)) * View;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_S && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
View = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -0.05f)) * View;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_E && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
CameraTilt += 0.1;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_Q && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
CameraTilt -= 0.1;
if (OperatingSystem.Keyboard.ModePressed == GLFW_MOD_SHIFT)
{
//Keys while Shift keys are also held down.
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_A && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
View = glm::translate(glm::mat4(), glm::vec3(0.1f, 0.0f, 0.0f)) * View;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_D && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
View = glm::translate(glm::mat4(), glm::vec3(-0.1f, 0.0f, 0.0f)) * View;
}
else
{
//Keys when shift keys are not being held down.
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_D && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
View = glm::rotate(glm::mat4(1.0f), 0.05f, glm::vec3(0.0f, 1.0f, 0.0f)) * View;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_A && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
View = glm::rotate(glm::mat4(1.0f), -0.05f, glm::vec3(0.0f, 1.0f, 0.0f)) * View;
}
//Yellow cube controls.
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_I && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
Cube.Transform(glm::translate(glm::mat4(), glm::vec3(0.0f, 0.0f, 0.05f)));
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_K && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
Cube.Transform(glm::translate(glm::mat4(), glm::vec3(0.0f, 0.0f, -0.05f)));
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_L && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
Cube.Transform(glm::rotate(glm::mat4(), glm::radians<float>(-1), glm::vec3(0.0f, 1.0f, 0.0f)));
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_J && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
Cube.Transform(glm::rotate(glm::mat4(), glm::radians<float>(1), glm::vec3(0.0f, 1.0f, 0.0f)));
if (CameraTilt > MaxTiltAngle) CameraTilt = MaxTiltAngle;
if (CameraTilt < -MaxTiltAngle) CameraTilt = -MaxTiltAngle;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_Y && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
Cube2Pivot = glm::rotate(glm::mat4(), glm::radians<float>(1), glm::vec3(0.0f, 0.0f, 1.0f)) * Cube2Pivot;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_H && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
Cube2Pivot = glm::rotate(glm::mat4(), glm::radians<float>(-1), glm::vec3(0.0f, 0.0f, 1.0f)) * Cube2Pivot;
Triangle.Transform(glm::rotate(glm::mat4(), glm::radians<float>(1), glm::vec3(0.0f, 1.0f, 0.0f)));
Cube2Pivot = Cube2Pivot * glm::rotate(glm::mat4(), glm::radians<float>(1), glm::vec3(0.0f, 1.0f, 0.0f));
Cube2.WorldMatrix = Cube.WorldMatrix * Cube2Pivot * Cube2World;
}
//=====================================================================================================================
You can probably ignore the last 3 lines, as they merely put some objects in the scene and animate them. This is actually my Update() method that executes every frame. I haven’t bothered to create a separate camera or UI class. At some point I would probably want to for an actual game as I might want to allow the user to remap all the controls or such. But to get started, I think this is a pretty good starting place.
I’m using GLFW instead of GLU, but I imagine the concepts are pretty similar. This is OGL 4.5. I wasn’t sure which version you are using.
Notice there is no LookAt() method. That’s actually used once at startup in the Initialize() method:
//=====================================================================================================================
// Game::Initialize()
//
// Purpose:
// To allow any code you want to run once at startup not associated with art assets.
//
// Input:
// None.
//
// Output:
// bool - The program will close if it returns false assuming a catastrophic error has occured.
//
// Notes:
//
//
//=====================================================================================================================
bool Game::Initialize()
{
bool GameObjectInitializedProperly = false; //Must be set to true to keep the program from closing.
CameraHeight = 1.68f; //Roughly the average eye height in meters of an average man. Our camera will stay at this level to make it feel like we are in the scene.
CameraTilt = 0.0f; //Will tilt the camera up and down.
GameObjectInitializedProperly = true; //This should probably be set by error checking, but we don't have any error checking here.
View = glm::lookAt(glm::vec3(0.0f, CameraHeight, 2.0f), glm::vec3(0.0f, CameraHeight, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); //1.63 meters is roughly the height of the average man's eyes.
//Projection = glm::perspective(0.96f, 1.770833f, 0.1f, 700.0f); //0.96 is 55 degrees and 1.7708333 is the width to height ratio on my computer.
Projection = glm::perspective(0.96f, OperatingSystem.AspectRatio(), 0.1f, 700.0f); //0.96 is 55 degrees and 1.7708333 is the width to height ratio on my computer.
glEnable(GL_CULL_FACE); //Turn back face culling on.
glCullFace(GL_BACK); //Set back face culling to remove the back face rather than the front face. Back is default. Included here for clarity.
glEnable(GL_DEPTH_TEST); //Enable the depth buffer so that things are drawn correctly.
glEnable(GL_FRAMEBUFFER_SRGB); //Needed to prepare frame buffer for sRGB color.
DiffuseLightDirection = glm::normalize(glm::vec3(1.0f, -1.0f, -1.0f)); //Direction that the primary light of the scene is "shining" in.
AmbientLightColor = glm::vec4(0.05f, 0.05f, 0.1f, 1.0f); //Light color in the "shadows".
DiffuseLightColor = glm::vec4(1.0f, 1.0f, 0.9f, 1.0f); //Direct light color.
return GameObjectInitializedProperly;
}
//=====================================================================================================================
Basically, I’m using the View matrix to be the camera and thus hold the camera’s position and orientation information from frame to frame. The UI merely adjusts the View matrix to rotate the camera.
I do a trick that I kind of like to limit the camera to tilt 45 degrees up or down. It’s largely implemented in my Draw() method:
glm::mat4 TiltedView = glm::rotate(glm::mat4(), CameraTilt, glm::vec3(1.0, 0.0, 0.0)) * View;
I’m actually drawing with “TiltedView” rather than the View matrix. So, at the very last minute before drawing, I apply the camera tilt. So, I’m not really ever tilting the camera up or down as the view matrix, just moving it through the scene. The yaw rotation is maintained in the View matrix. The pitch is kept separately in order to allow me to limit it easily. In the Update() a simple less than greater than keeps the tilt in range. I never roll the camera. It’s the best way I’ve come up with so far that uses the View matrix to control the camera and limits the range of motion.
Anyway, this is one way of doing it. I think it’s one of the more efficient ways of doing it; you’ll notice the camera code is so simple that much of it’s part of the same line of code as the UI. Thought it might give you some ideas you could work with.
Some things that are really missing here are a timer to use the time since the last frame occurred to keep animation at a constant rate. And so the rates are hard coded. That’s probably not good, but it works to get started.
But all the code is there for a game pad or keyboard. You just need to add code for a mouse which shouldn’t be too much different than what’s already there.
The complete Visual Studio 2015 project is on my website at VirtuallyProgramming.com available for download. So, you can see the code in action. There’s also a video clip on that page where you can see what the code does without downloading the actual code.
EDIT: Just noticed that some of the comments in the code are way out in left field, especially in the headers. I copy and paste code from one version to the next and that Update() header looks like it came from a DirectX version of the code that worked differently. So take some of the comments with a grain of salt.