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などのライブラリが役立ちます。