HOWTO · C++

C++ で 2つのベクトル間の角度を計算する

このチュートリアルでは、C++ で 2つのベクトル間の角度を計算するプロセスを示します。

このページの内容

同じ次元を持つ2つのベクトルの主角は、内積を使って計算できます。ベクトル u と v に対する式は theta = acos((u · v) / (|u| * |v|)) です。C++では内積とそれぞれの大きさを計算し、浮動小数点の丸め誤差に備えてコサイン値を [-1, 1] の範囲に収めてから、std::acos に渡します。結果はラジアンで返されます。度が必要な場合は 180 / pi を掛けてください。

内積と角度の公式を理解する

ベクトルは、(x, y) や (x, y, z) のような成分を順に並べたものです。ベクトルの大きさは長さで、各成分を二乗して合計した値の平方根です。2つのベクトルの内積は、対応する成分どうしの積を合計した値です。たとえば、u · v は u[0] * v[0] + u[1] * v[1] + ... となります。

角度のコサインは、内積を2つのベクトルの大きさの積で割った値です。その逆余弦(acos)を求めると、符号のない主角が得られます。範囲は0度から180度(または0からπラジアン)です。この公式は、2つのベクトルの成分数が同じであれば、2次元、3次元、それ以上の次元で使えます。時計回りか反時計回りかといった向きは、この公式では求められません。

C++配列で角度を計算する

この例では、宣言時の長さが同じ2つの組み込み配列を使います。ゼロベクトルとの角度は定義されないため、どちらかのベクトルの大きさが0なら、関数は false を返します。それ以外の場合はコサインを計算して数学的に有効な範囲に収め、結果を度で出力します。

次の例を実行します。

#include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>

bool calculateAngleDegrees(const double u[], const double v[], int size,
                           double& angleDegrees) {
    double dot = 0.0;
    double squaredU = 0.0;
    double squaredV = 0.0;

    for (int i = 0; i < size; ++i) {
        dot += u[i] * v[i];
        squaredU += u[i] * u[i];
        squaredV += v[i] * v[i];
    }

    if (squaredU == 0.0 || squaredV == 0.0) {
        return false;
    }

    double cosine = dot / std::sqrt(squaredU * squaredV);
    cosine = std::max(-1.0, std::min(1.0, cosine));
    const double pi = std::acos(-1.0);
    angleDegrees = std::acos(cosine) * 180.0 / pi;
    return true;
}

int main() {
    const int size = 3;
    const double u[size] = {1.0, 0.0, 0.0};
    const double v[size] = {0.0, 1.0, 0.0};
    double angleDegrees = 0.0;

    if (calculateAngleDegrees(u, v, size, angleDegrees)) {
        std::cout << std::fixed << std::setprecision(1)
                  << "Angle: " << angleDegrees << " degrees\n";
    } else {
        std::cout << "An angle requires two nonzero vectors.\n";
    }
}

サンプルの2つのベクトルは直交しているため、角度は90度になります。出力例の Angle: 90.0 degrees という表示ラベルは、バイト単位で保持されたコードおよび出力例の一部のため、英語のままです。

std::arrayで角度を計算する

std::array は固定サイズの並びを表し、要素数を型に持ちます。この例はC++11以降で使えます。配列の型から要素数を取得するため、2つの入力の成分数は同じです。計算方法とゼロベクトルの処理は、組み込み配列を使う例と同じです。

#include <algorithm>
#include <array>
#include <cmath>
#include <iomanip>
#include <iostream>

bool calculateAngleDegrees(const std::array<double, 3>& u,
                           const std::array<double, 3>& v,
                           double& angleDegrees) {
    double dot = 0.0;
    double squaredU = 0.0;
    double squaredV = 0.0;

    for (std::size_t i = 0; i < u.size(); ++i) {
        dot += u[i] * v[i];
        squaredU += u[i] * u[i];
        squaredV += v[i] * v[i];
    }

    if (squaredU == 0.0 || squaredV == 0.0) {
        return false;
    }

    double cosine = dot / std::sqrt(squaredU * squaredV);
    cosine = std::max(-1.0, std::min(1.0, cosine));
    const double pi = std::acos(-1.0);
    angleDegrees = std::acos(cosine) * 180.0 / pi;
    return true;
}

int main() {
    const std::array<double, 3> u = {1.0, 0.0, 0.0};
    const std::array<double, 3> v = {0.0, 1.0, 0.0};
    double angleDegrees = 0.0;

    if (calculateAngleDegrees(u, v, angleDegrees)) {
        std::cout << std::fixed << std::setprecision(1)
                  << "Angle: " << angleDegrees << " degrees\n";
    } else {
        std::cout << "An angle requires two nonzero vectors.\n";
    }
}

次元数がコンパイル時に決まっている場合は、std::array<double, N> を使えます。これによって計算自体が本質的に高速になるわけではありません。ここでの主な利点は、サイズが型に含まれ、.size() で取得できることです。

ゼロベクトル、次元数、有向角への対処

どちらかのベクトルの大きさが0なら、角度は定義されません。例のように、大きさの積で割る前にゼロベクトルかどうかを確認してください。組み込み配列では、両方に共通する実際の長さを渡し、各配列に少なくともその数の要素があることを確かめます。std::array では、両方の型で同じ N を使うことで次元数が一致します。

内積の公式で求まるのは、符号のない主角です。2次元の有向角は atan2(cross, dot) で計算できます。ここでスカラーの外積は u.x * v.y - u.y * v.x です。3次元では、選んだ単位法線 n に垂直な平面上に両方のベクトルがある場合、符号付き角度の公式 atan2(n · (u × v), u · v) で、その平面内の有向角を求められます。どちらかのベクトルが対象の平面上にない場合は、まず両方をその平面に射影してから、射影したベクトルに公式を適用します。この場合に求まるのは、射影後のベクトルどうしの角度です。法線は正の向きを定めるものであり、内積の公式に追加するものではありません。これらの有向角の計算結果もラジアンなので、前述と同じ係数で度に変換できます。

よくある質問

内積から何がわかりますか? 対応する成分どうしを組み合わせて、1つのスカラー値を得られます。ベクトルの大きさとの関係から、この値は角度のコサインを決めます。主角が鋭角なら正、ベクトルが直交していれば0、鈍角なら負です。

std::acos は度を返しますか? いいえ。ラジアンを返します。ラジアンに 180 / pi を掛けると度に変換できます。プログラムのほかの部分でラジアンを使う場合は、そのまま扱ってもかまいません。

ベクトルの次元数が異なっていても計算できますか? いいえ。内積では対応する成分どうしを組み合わせるため、ベクトルの次元数は同じでなければなりません。組み込み配列では、両方に共通する正しい長さを指定します。std::array では、サイズが型に示されます。

この公式は3次元でも使えますか? はい。内積と大きさを使う公式は、3次元を含む、同じ次元数のベクトルに適用できます。向きが必要な場合に限り、基準となる法線を使って有向角を計算してください。

ベクトル演算ライブラリを使うべきですか? 小規模な計算なら、ここで紹介した例で十分です。プロジェクトですでにベクトル型や行列型を使っている場合や、より幅広い演算が必要な場合は、EigenやGLMなどのライブラリが役立ちます。