4

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.

Problem

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.

Code

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;

    this._updateCameraVectors();
    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;
        return;
    }

    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;
    }

    this._updateCameraVectors();
}

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);
    }

    // ...

}

Question

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.

チーズパン
  • 27
  • 2
  • 14
  • 1
    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. – jeremyong Nov 19 '18 at 00:13

1 Answers1

-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]));
joe_chip
  • 237
  • 1
  • 5