This will be the final technical article of the series where we will add some very basic user input to allow us to move around in our 3D scene with the keyboard. This article felt like a nice way to finish this series as our scene has been pretty static so far. The goals are:

- Introduce a new
*player*class which holds the position and orientation of the player in the 3D world. - Update our existing
*perspective camera*class to allow the ability for its position and orientation to be set, which we’ll do based on the player. - Add input processing to scan for keys being pressed, allow our player class to move and rotate, which our camera will follow.

Our player class will represent the model of the entity in our 3D world which can move around or perform actions. A player will have a position and an orientation. We will also give it some functions to mutate its state such as *move forward* or *turn left*. We will be keeping the player class very simple though in a more complex application we may instead choose to generalise it as an *entity* and compose it with position and movement traits. If you are interested in this kind of idea you can read up on *Entity Component Systems* which I actually find quite fascinating: https://www.gamedev.net/articles/programming/general-and-gameplay-programming/understanding-component-entity-systems-r3013.

Let’s not muck about, create `scene/player.hpp`

and `scene/player.cpp`

. We are placing the player class in the `scene`

folder as it isn’t really a *core* concept. Edit `player.hpp`

with the following structure:

```
#pragma once
#include "../core/glm-wrapper.hpp"
#include "../core/internal-ptr.hpp"
namespace ast
{
struct Player
{
Player(const glm::vec3& position);
void moveForward(const float& delta);
void moveBackward(const float& delta);
void moveUp(const float& delta);
void moveDown(const float& delta);
void turnLeft(const float& delta);
void turnRight(const float& delta);
glm::vec3 getPosition() const;
glm::vec3 getDirection() const;
private:
struct Internal;
ast::internal_ptr<Internal> internal;
};
} // namespace ast
```

Most of this should be fairly intuitive. We initialise the player with a `position`

and can command it to move around in different ways. Notice that we pass in a `delta`

to all the move operations which allows us to perform frame rate independent calculations. The player class will also expose its `position`

and `direction`

.

Edit `player.cpp`

to fill in the implementation:

```
#include "player.hpp"
using ast::Player;
namespace
{
glm::mat4 computeOrientation(const glm::mat4& identity, const float& rotationDegrees, const glm::vec3& up)
{
return glm::rotate(identity, glm::radians(rotationDegrees), up);
}
glm::vec3 computeForwardDirection(const glm::mat4& orientation)
{
return glm::normalize(orientation * glm::vec4(0, 0, 1, 0));
}
} // namespace
struct Player::Internal
{
const glm::mat4 identity;
const glm::vec3 up;
const float moveSpeed{5.0f};
const float turnSpeed{120.0f};
float rotationDegrees;
glm::vec3 position;
glm::mat4 orientation;
glm::vec3 forwardDirection;
Internal(const glm::vec3& position)
: identity(glm::mat4(1)),
up(glm::vec3{0.0f, 1.0f, 0.0f}),
rotationDegrees(0.0f),
position(position),
orientation(::computeOrientation(identity, rotationDegrees, up)),
forwardDirection(::computeForwardDirection(orientation)) {}
void moveForward(const float& delta)
{
position -= forwardDirection * (moveSpeed * delta);
}
void moveBackward(const float& delta)
{
position += forwardDirection * (moveSpeed * delta);
}
void turnLeft(const float& delta)
{
rotate(turnSpeed * delta);
}
void turnRight(const float& delta)
{
rotate(-turnSpeed * delta);
}
void moveUp(const float& delta)
{
position.y += (moveSpeed * delta);
}
void moveDown(const float& delta)
{
position.y -= (moveSpeed * delta);
}
void rotate(const float& amount)
{
rotationDegrees += amount;
if (rotationDegrees > 360.0f)
{
rotationDegrees -= 360.0f;
}
else if (rotationDegrees < 0.0f)
{
rotationDegrees += 360.0f;
}
orientation = ::computeOrientation(identity, rotationDegrees, up);
forwardDirection = ::computeForwardDirection(orientation);
}
};
Player::Player(const glm::vec3& position) : internal(ast::make_internal_ptr<Internal>(position)) {}
void Player::moveForward(const float& delta)
{
internal->moveForward(delta);
}
void Player::moveBackward(const float& delta)
{
internal->moveBackward(delta);
}
void Player::turnLeft(const float& delta)
{
internal->turnLeft(delta);
}
void Player::turnRight(const float& delta)
{
internal->turnRight(delta);
}
glm::vec3 Player::getPosition() const
{
return internal->position;
}
glm::vec3 Player::getDirection() const
{
return internal->forwardDirection;
}
void Player::moveUp(const float& delta)
{
internal->moveUp(delta);
}
void Player::moveDown(const float& delta)
{
internal->moveDown(delta);
}
```

Each of the command functions will change either the `position`

or the `orientation`

. The position is fairly simple, holding `x, y, z`

coordinates in 3D space. To move *forward* we need to know what the current *orientation* is based on the angle we are pointing at. We use the following formula to calculate the *orientation matrix* using the current value of `rotationDegrees`

and an `up`

vector:

```
glm::mat4 computeOrientation(const glm::mat4& identity, const float& rotationDegrees, const glm::vec3& up)
{
return glm::rotate(identity, glm::radians(rotationDegrees), up);
}
```

We need to convert the `rotationDegrees`

into radians as GLM works with radians. The `up`

vector for us will be fixed as `(0, 1, 0)`

meaning we are always rotating around the *y* axis. In a game that allows more free form rotation you might instead use *quaternions* but we won’t bother for our use case. The result of performing the `glm::rotate`

operation using the `identity`

matrix as a base is the *orientation matrix*. We can use the orientation matrix to perform direction based calculations such as finding out which way is *forward*.

The formula to calculate which direction is *forward* given an orientation matrix is below:

```
glm::vec3 computeForwardDirection(const glm::mat4& orientation)
{
return glm::normalize(orientation * glm::vec4(0, 0, 1, 0));
}
```

The orientation matrix is multiplied with a `vec4`

where we place the value of `1`

in the coordinate we care about, which is the `z`

coordinate at the third position. The fourth argument to the `vec4`

is required as the orientation matrix is a 4 x 4 matrix so we need 4 components. The result is a `vec3`

which includes the magnitude of `x, y, z`

that represents the direction. Is is also very important that we `normalize`

the resulting vector so it becomes a *unit vector* meaning that the magnitude of the vector along any of its axes never exceeds `1`

- all magnitudes in the vector are scaled appropriately to meet this criteria. This is really useful when using a vector to perform other calculations such as the next ones about moving the player.

The code to move forward or backward is almost the same but essentially we take the `moveSpeed`

multiplied by the `delta`

which gives us the speed to apply in a frame independent way, multiplied by the `forwardDirection`

vector which as mentioned is a *unit vector*. The resulting vector is then added or subtracted from the current position to translate it:

```
void moveForward(const float& delta)
{
position -= forwardDirection * (moveSpeed * delta);
}
void moveBackward(const float& delta)
{
position += forwardDirection * (moveSpeed * delta);
}
```

To rotate the player we will do a very simple calculation by adding the amount to rotate to our `rotationDegrees`

field, wrapping it so it stays within the `0..360`

range then subsequently regenerating our *orientation matrix* and *forward direction* to sync with the new rotation:

```
void rotate(const float& amount)
{
rotationDegrees += amount;
if (rotationDegrees > 360.0f)
{
rotationDegrees -= 360.0f;
}
else if (rotationDegrees < 0.0f)
{
rotationDegrees += 360.0f;
}
orientation = ::computeOrientation(identity, rotationDegrees, up);
forwardDirection = ::computeForwardDirection(orientation);
}
```

The rest of this class is fairly self explanatory.

Currently our perspective camera class has a hard coded position and orientation that is calculated during its construction. We don’t yet expose a way to tell our camera to move somewhere else, or look somewhere else. We’d like to do this so we can feed in the player’s position and direction so the camera effectively *tracks* the player. We could have put all the code from the *player* class directly into our camera, but that means our camera could never do anything except represent the player - imagine if we wanted the camera to detach itself from the player and fly around the scene on its own - by decoupling the player (*model*) from the camera (*view*) we can do all sorts of tricks with the camera later without affecting the player itself.

We’ll be making a reasonable number of changes to our camera so I’ll put the entire code again here. First up, edit `core/perspective-camera.hpp`

and replace with the following:

```
#pragma once
#include "../core/glm-wrapper.hpp"
#include "../core/internal-ptr.hpp"
namespace ast
{
struct PerspectiveCamera
{
PerspectiveCamera(const float& width, const float& height);
void configure(const glm::vec3& position, const glm::vec3& direction);
glm::mat4 getProjectionMatrix() const;
glm::mat4 getViewMatrix() const;
private:
struct Internal;
ast::internal_ptr<Internal> internal;
};
} // namespace ast
```

The `configure`

function has been introduced which allows the camera to be given a new position and direction. The other functions are the same as before except I’ve removed the `const&`

qualifiers from the getters. This is mainly because internally the *view matrix* will calculated dynamically instead of at construction time and to be consistent I’d done the same thing for the *projection matrix*.

Edit `perspective-camera.cpp`

and replace with the following:

```
#include "perspective-camera.hpp"
using ast::PerspectiveCamera;
struct PerspectiveCamera::Internal
{
const glm::mat4 projectionMatrix;
const glm::vec3 up;
glm::vec3 position;
glm::vec3 target;
Internal(const float& width, const float& height)
: projectionMatrix(glm::perspective(glm::radians(60.0f), width / height, 0.01f, 100.0f)),
up(glm::vec3{0.0f, 1.0f, 0.0f}) {}
};
PerspectiveCamera::PerspectiveCamera(const float& width, const float& height)
: internal(ast::make_internal_ptr<Internal>(width, height)) {}
void PerspectiveCamera::configure(const glm::vec3& position, const glm::vec3& direction)
{
internal->position = position;
internal->target = position - direction;
}
glm::mat4 PerspectiveCamera::getProjectionMatrix() const
{
return internal->projectionMatrix;
}
glm::mat4 PerspectiveCamera::getViewMatrix() const
{
return glm::lookAt(internal->position, internal->target, internal->up);
}
```

There are a fair few more changes in the implementation. We still compute the `projectionMatrix`

and `up`

vector during construction but we no longer keep a constant `position`

or `target`

. The `configure`

function takes a new `position`

and stores it, then computes a new `target`

by taking the current camera position and subtracting the `direction`

. The `target`

is still used when calculating the view matrix which we do using the `glm::lookAt`

function as before, only this time it will be computed any time the `getViewMatrix`

function is invoked.

We now have a player and a camera that can be mutated so we can update our main scene to use them. Edit `scene/main-scene.cpp`

and add the following header includes:

```
#include "../core/sdl-wrapper.hpp"
#include "player.hpp"
```

Now go to the `Internal`

structure and add a new field to hold our player instance, initialising it with a position of `(0, 0, 2)`

:

```
struct SceneMain::Internal
{
...
ast::Player player;
Internal(const ast::WindowSize& size)
: ...
player(ast::Player(glm::vec3{0.0f, 0.0f, 2.0f})) {}
```

Edit the `update`

function, adding a new call to the `camera.configure`

function before we get the camera matrix. Note that we pass in the player position and direction:

```
struct SceneMain::Internal
{
...
void update(const float& delta)
{
camera.configure(player.getPosition(), player.getDirection());
const glm::mat4 cameraMatrix{camera.getProjectionMatrix() * camera.getViewMatrix()};
```

Run the application and everything should render exactly the same as before though now we are feeding our camera with the position and direction of our player. This means that any time we adjust the position or direction of our player the camera will automatically be updated to follow along with it.

To move our player we will add a new function to our main scene to handle input, looking for particular key scan codes to know which commands to run on our player.

We will be using an SDL function named `SDL_GetKeyboardState`

to find out what keys are currently being pressed on the keyboard. The keyboard state itself is held internally inside SDL - the `SDL_GetKeyboardState`

function simply returns to us a pointer to it. We will grab this pointer and store it in a field so we don’t have to keep asking for it. Add the following internal field to hold the keyboard state pointer and initialise it in the constructor like so:

```
struct SceneMain::Internal
{
...
const uint8_t* keyboardState;
Internal(const ast::WindowSize& size)
: ...
keyboardState(SDL_GetKeyboardState(nullptr)) {}
```

Note: We do not need to destroy the keyboard state as we do not own it - the main SDL runtime owns it and will manage its lifecycle. We just need to keep a pointer to it so we can access it.

Add the following function to use our keyboard state:

```
struct SceneMain::Internal
{
...
void processInput(const float& delta)
{
if (keyboardState[SDL_SCANCODE_UP])
{
player.moveForward(delta);
}
if (keyboardState[SDL_SCANCODE_DOWN])
{
player.moveBackward(delta);
}
if (keyboardState[SDL_SCANCODE_A])
{
player.moveUp(delta);
}
if (keyboardState[SDL_SCANCODE_Z])
{
player.moveDown(delta);
}
if (keyboardState[SDL_SCANCODE_LEFT])
{
player.turnLeft(delta);
}
if (keyboardState[SDL_SCANCODE_RIGHT])
{
player.turnRight(delta);
}
}
```

I think this code should explain itself pretty clearly, we are just looking in the keyboard state for various `SDL_SCANCODE`

values and firing the appropriate commands on the player object as needed. Lastly we need to call our new `processInput`

function when we update the scene. Edit the `update`

function and invoke `processInput`

as the first thing that we do like this:

```
void update(const float& delta)
{
processInput(delta);
camera.configure(player.getPosition(), player.getDirection());
const glm::mat4 cameraMatrix{camera.getProjectionMatrix() * camera.getViewMatrix()};
```

Run your application again and you should now be able to use the arrow keys to move around and press `A`

or `Z`

to move up and down! Try it below in the Emscripten version:

This concludes the *A Simple Triangle* series at least for now. I am writing this article in October 2019 which is over a year since I started on the research and development represented throughout this series. I have learned a massive amount about OpenGL, Vulkan, SDL and cross platform C++ development spanning MacOS, Windows, Android, iOS and browsers and I hope that by authoring my learnings I can help others who might be interested in these topics too.

It took a long to time to complete my goals as I only had pockets of spare time to invest in it outside my normal day job and family life. To be sure there were absolutely points in time where I lost interest and motivation and I had to take a break from it, but I’m happy that I followed through to its conclusion.

There are many subsequent and related topics that I’d like to explore further including:

- Revisiting the asset manager and move away from enums in favour of something more dynamic to allow a data driven engine.
- Implementing 2D rendering including text which would be needed for GUI screens, menus, heads up displays and so on.
- Adding advanced input handling at the scene level including touch interaction.
- Adding lighting and improved shaders and shader pipelines.
- Adding animations to 3D mesh objects and allowing for dynamic 3D meshes.
- Application of scene model culling techniques such as BSP trees.
- Optimizing some of the hot code paths particularly in the renderer to avoid multiple draw calls.
- Optimizing the loading of mesh and texture data - at the moment we created new memory buffers for each individual mesh, but really we should try to create a minimal number of larger shared buffers that hold all our 3D scene data. We could apply run time texture packing as well to turn multiple textures into single textures ahead of time.
- Sound and music.
- Physics.
- Networking.
- The list goes on!!

In the future I might pick up some of these topics and add some more articles to the series about them but for now I’ll stop at this point.

Happy coding!!!

The code for this article can be found here.

End of part 30