I am following this tutorial on how to implement a first person camera. So far I was able to implement the entire (C++) class in TypeScript and it works as it is supposed to.


What I was not able to implement is a system which takes the camera direction only and initially orients the camera based on it. In the tutorial the author does set the camera direction to (0.0, 0.0, -1.0) but it is instantly overwritten by calling _updateCameraVectors() in the constructor. So as I see it, passing the camera direction to the class will always be ignored. Before looking at the code please note:

  • In the tutorial the author referes to a variable Front, which in my case is called cameraDirection
  • In the tutorial only the view matrix is being calculated by the camera class. I am calculating the view-projection matrix.


Here are the most important parts:

The constructor initializes all the important stuff, but most importantly calls _updateCameraVectors().

constructor(gl: WebGL2RenderingContext, program: WebGLProgram, position: vec3, cameraDirection = vec3.fromValues(0.0, 0.0, -1.0),
    yaw = -90.0, pitch = 0.0,
    aspect = gl.canvas.clientWidth / gl.canvas.clientHeight, zNear = 1, zFar = 2000) {
    this.gl = gl;
    this._program = program;
    this._aspect = aspect;
    this._zNear = zNear;
    this._zFar = zFar;
    this._yaw = yaw;
    this._pitch = pitch;
    this._cameraPosition = position;
    this._cameraDirection = cameraDirection;

    GLHelper.setUniformLocations(this.gl, this._unifroms, this._program);

In _updateCameraVectors() the camera direction is being recalculated based on yaw and pitch values. Then the cameraUp vector is calculated.

protected _updateCameraVectors(): void {
    const front = vec3.create();
    front[0] = Math.cos(MathHelper.degToRad(this._yaw)) * Math.cos(MathHelper.degToRad(this._pitch));
    front[1] = Math.sin(MathHelper.degToRad(this._pitch));
    front[2] = Math.sin(MathHelper.degToRad(this._yaw)) * Math.cos(MathHelper.degToRad(this._pitch));
    vec3.normalize(this._cameraDirection, front);

    const right = vec3.create();
    vec3.cross(right, this._cameraDirection, this._worldUp); // World up is always (0,1,0)
    vec3.normalize(this._cameraRight, right);
    const up = vec3.create();
    vec3.cross(up, this._cameraRight, this._cameraDirection);
    vec3.normalize(this._cameraUp, up);

Based on a requestAnimationFrame-loop the _computeviewProjectionMatrix simply calculates a view-projection matrix which is later used for rendering.

private _computeviewProjectionMatrix(): mat4 {

    this._aspect = this.gl.canvas.clientWidth / this.gl.canvas.clientHeight;

    const cameraTarget = vec3.create();
    vec3.add(cameraTarget, this._cameraPosition, this._cameraDirection); // Here the actual camera target is calculated.

    const view = mat4.create();
    mat4.lookAt(view, this._cameraPosition, cameraTarget, this._cameraUp);

    const projectionMatrix = mat4.create();

    mat4.perspective(projectionMatrix, this._fieldOfViewRadians, this._aspect, this._zNear, this._zFar);

    const viewProjectionMatrix = mat4.create();
    mat4.multiply(viewProjectionMatrix, projectionMatrix, view);

    return viewProjectionMatrix;

The _handleMouseMovement function calls _updateCameraVectors() every time the pitch and yaw values are updated via mouse movement.

private _handleMouseMovement = (event: MouseEvent): void => {

    if (this._firstMouseMove) {
        this._firstMouseMove = false;

    let xOffset = event.movementX;
    let yOffset = event.movementY;

    xOffset *= this._sensitivity;
    yOffset *= this._sensitivity;

    this._yaw += xOffset;

    if (!this._inverseY) {
        this._pitch += yOffset;
    } else {
        this._pitch -= yOffset;

    if (this._pitch > 89) {
        this._pitch = 89;
    if (this._pitch < -89) {
        this._pitch = -89;


Last but not least there is a _moveCamera function which updates the camera position based on WASD input. I will post only a excerpt because it is selfexplonatory.

private _moveCamera(): void {
    // INFO: 'W' - Move forward
    if (this._keyMap[87] === true) {
        const factor = vec3.create();
        vec3.scale(factor, this._cameraDirection, this._cameraSpeed * this._deltaTime);
        vec3.add(this._cameraPosition, this._cameraPosition, factor);

    // ...



How would I modify this class to initially "lookAt" a vec3 point passed to the constructor? Note I am not really asking for code only. Suggestion, ideas and any advice would be really helpful.

  • 1
    $\begingroup$ Haven't read through the entire question yet but note that just a view direction is underspecified for the full camera matrix as it does not account for the roll (aka the "up direction" of the camera. $\endgroup$
    – jeremyong
    Nov 19, 2018 at 0:13

1 Answer 1


Given that you already use yaw and pitch angles to describe the camera, the simplest solution is to compute yaw and pitch values for the vector (note: it must be a unit vector).

If you have front vector given, you can compute angles:

pitch = MathHelper.radToDeg(Math.asin(front[1]));
yaw = MathHelper.radToDeg(Math.atan2(front[2], front[1]));

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.