鉢本 一二のブログ

OpenGLのマウス操作の実装メモ

作成日 :
更新日 :
local_offer OpenGL

2022/01/30
微修正


3D画面をマウス操作したときのカメラの動きの実装をメモ。
自分的にはいい感じに動いているが、もしかしたら間違いがあるかもしれない。

カメラは以下のようにした場合の実装のメモとなっている。

struct Camera
{
    enum ZoomType
    {
        IN,
        OUT,
    };

    glm::vec3 pos = glm::vec3(1, 1, 1); // 位置
    glm::vec3 center = glm::vec3(0);    // 注視点
    glm::vec3 up = glm::vec3(0, 1, 0);  // 上方向
    glm::uvec2 size;                    // ウィンドウサイズ

    // ズーム
    void zoom(ZoomType type) noexcept;
    // 移動
    void move(const glm::dvec2& mouse_pos0, const glm::dvec2& mouse_pos1) noexcept;
    // 回転
    void revolution(const glm::dvec2& old_cursor_pos, const glm::dvec2& new_cursor_pos) noexcept
};    

ズーム

ズームは、カメラの注視点(center) に向かってに カメラの位置(pos) を移動させるようにした。

ズームイン

ズームインは、カメラの位置(pos) が カメラの注視点(center) へ近づく

ズームイン

ズームアウト

ズームアウトは、カメラの位置(pos) が カメラの注視点(center) から離れる

ズームアウト

実装

void Camera::zoom(ZoomType type) noexcept
{
    auto d = 1.0f;
    if(type == ZoomType::IN) d = -1.0f;  // ズームインだと近づくのでマイナスにする

    // 移動方向を求める
    const auto dir = glm::normalize(pos - center);

    // 新しい位置を求める
    const auto mouse_pos1 = pos + dir * d;

    // カメラがズームインできる最小値を設定. 0.3fは適当に設定
    // ズームアウトは今回とくに制限を設けない
    const float min_near = znear + 0.3f;
    if(glm::distance(mouse_pos1, center) > min_near)
    {
        pos = mouse_pos1;
    }
}

移動

カメラの移動は、カメラの位置(pos)と注視点(center)が共に移動するように実装する。

実装

void Camera::move(const glm::dvec2& mouse_pos0, const glm::dvec2& mouse_pos1) noexcept
{
    const auto view = glm::lookAt(pos, center, up);
    const auto proj = glm::perspectiveFov(glm::radians(fovy),
                                          static_cast<float>(size.x),
                                          static_cast<float>(size.y),
                                          znear,
                                          zfar);
    const auto viewport = glm::vec4(0, 0, size.x, size.y);

    // マウス座標をワールド座標に変換
    const auto p0 = glm::unProject(
        glm::vec3(mouse_pos0.x/2 - mouse_pos0.x, mouse_pos0.y - mouse_pos0.y/2, 0),
        view, proj, viewport
    );
    const auto p1 = glm::unProject(
        glm::vec3(mouse_pos1.x/2 - mouse_pos1.x, mouse_pos1.y - mouse_pos1.y/2, 0),
        view, proj, viewport
    );

    // マウス移動した分をカメラの位置と注視点に追加
    constexpr float SPEED = 30.0f;
    const auto d = (p1 - p0) * (glm::distance(center, pos) * SPEED);
    pos += d;
    center += d;
}

回転

カメラの回転は、注視点(center)を中心とした球体に沿ってカメラが移動するようにする。
この時、カメラの位置(pos)と上方向(up)が変化する。
球面座標 で求められるらしい。
よくわかっていない。参考にしたサイトがあったがメモするのを忘れてしまった。

回転

とりあえず、ソースコードを乗っける。

実装

void Camera::revolution(const glm::dvec2& mouse_pos0, const glm::dvec2& mouse_pos1) noexcept
{
    constexpr auto SPEED = 10.0f;

    const auto u = glm::normalize(up);
    const auto w = glm::normalize(glm::cross(u, pos));

    const float theta_u = (mouse_pos0.x - mouse_pos1.x) / SPEED;
    auto q = glm::angleAxis(glm::radians(theta_u), u);
    auto tmp_pos = q * pos * glm::conjugate(q);

    const float theta_w = -(mouse_pos1.y - mouse_pos0.y) / SPEED;
    q = glm::angleAxis(glm::radians(theta_w), w);
    const auto p = glm::conjugate(q);
    tmp_pos = q * tmp_pos * p;

    pos = tmp_pos;
    up = q * up * p;
}