Skip to content

Camera::setFront() is a no-op: the assignment is overwritten by updateView() #91

Description

@killerdevildog

Summary

Camera::setFront() cannot change the camera's orientation. The vector it writes to front is unconditionally overwritten by updateView() on the very next statement, so the function is input-independent — every call leaves the camera exactly as it was.

Details

src/limitless/camera.cpp:

void Camera::setFront(const glm::vec3& _front) noexcept {
    front = _front;

    updateView();
}

updateView() recomputes front from yaw/pitch without reading the previous value:

void Camera::updateView() noexcept {
    // euler angles
    front.x = glm::cos(glm::radians(yaw)) * cos(glm::radians(pitch));
    front.y = glm::sin(glm::radians(pitch));
    front.z = glm::sin(glm::radians(yaw)) * cos(glm::radians(pitch));
    front = glm::normalize(front);
    ...

yaw and pitch are only ever written by the constructor and by mouseMove() (which applies relative deltas), so there is currently no way to set an absolute camera orientation from a direction vector.

Origin

This looks like a regression rather than intent. setFront() worked when it was introduced — the original updateView() was quaternion-based and used front as an input:

front = glm::normalize(pitch_quaternion * yaw_quaternion * front);

Commit 7f7229a ("cube demo") replaced that with the Euler formulation above, moving front from the right-hand side to the left. The assignment in setFront() became a dead store at that point, silently and without a compiler warning.

Impact

front feeds the view matrix (view = glm::lookAt(position, position + front, up)), which propagates to:

  • the scene UBO (view, view_inverse, VP) in src/limitless/renderer/scene_data.cpp
  • frustum culling via Frustum::fromCamera()
  • cascade shadow split frustums, which also read camera.getFront() directly
  • CameraMovement::Forward / Backward translation

The in-tree caller is samples/gltf_viewer/gltf_viewer.cpp:110, which calls camera.setFront({1.f, 0.f, 0.f}) to look down +X. It silently keeps the constructor defaults (pitch = -60, yaw = 270) instead.

Reproduction

Headless — Camera's constructor and updateView() are pure glm, so no GL context is required:

#include <limitless/camera.hpp>
#include <cstdio>

int main() {
    Limitless::Camera camera{{1920, 1080}};

    const auto before = camera.getFront();
    camera.setFront({1.0f, 0.0f, 0.0f});
    const auto after = camera.getFront();

    printf("before: (%.4f, %.4f, %.4f)\n", before.x, before.y, before.z);
    printf("after:  (%.4f, %.4f, %.4f)\n", after.x, after.y, after.z);
}

Output:

before: (0.0000, -0.8660, -0.5000)
after:  (0.0000, -0.8660, -0.5000)

after should be (1.0000, 0.0000, 0.0000). Every input produces this same constructor-default result, including {0, 1, 0} and {0, -1, 0}.

Suggested fix

Set yaw/pitch from the direction so that updateView() reproduces it, inverting the same Euler convention updateView() already uses:

void Camera::setFront(const glm::vec3& _front) noexcept {
    const auto direction = glm::normalize(_front);

    pitch = glm::degrees(glm::asin(direction.y));
    yaw = glm::degrees(glm::atan(direction.z, direction.x));

    updateView();
}

This round-trips exactly for arbitrary directions, and the resulting basis stays finite, orthogonal and unit-length in the straight-up/straight-down cases (glm::cos(glm::radians(90.0f)) is not exactly zero in float, so cross(front, world_up) does not degenerate).

I have this fix working locally and am happy to open a PR.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions