向量点乘、叉乘的理论、应用及代码实现(C++)
向量点乘、叉乘的理论、应用及代码实现(C++)
向量点乘和叉乘是计算机图形学和物理学中的重要概念,广泛应用于判断向量关系、计算投影和距离等场景。本文将从理论、应用到代码实现,全面介绍向量点乘和叉乘的相关知识。
前言
向量具有大小和方向。共线向量:两个平行的向量为共线向量。
1.1 叉积 Cross Product
a ⃗ × b ⃗ = ∣ a ⃗ ∣ ∣ b ⃗ ∣ sin θ n ⃗ \vec{a}\times\vec{b}=|\vec{a}||\vec{b}|\sin{\theta}\vec{n}a×b=∣a∣∣b∣sinθn
θ \thetaθ是两个向量之间的角度,n ⃗ \vec{n}n是与两个向量都垂直的单位向量,方向遵循右手定则(右手食指从a ⃗ \vec{a}a划到b ⃗ \vec{b}b,大拇指的方向)。
两个向量的叉积结果是一个与两者都垂直的向量。叉积的幅度值大小等于由这两个向量为边组成的平行四边形的面积。当两个向量垂直时,大小也达到最大,及矩形的面积。(这个特性决定了他可以用来计算空间中一个点到一个直线的距离,利用几何中平行四边形的面积同时等于底乘高,后面会介绍。)
三维空间中,叉积的结果也可以用3x3矩阵的行列式表示。
a ⃗ × b ⃗ = det ( i ⃗ , j ⃗ , k ⃗ ; a 1 , a 2 , a 3 ; b 1 , b 2 , b 3 ) = ( a 2 b 3 − a 3 , b 2 ) i ⃗ + ( a 3 b 1 − a 1 , b 3 ) j ⃗ + ( a 1 b 2 − a 2 , b 1 ) k ⃗ \vec{a}\times \vec{b}=\det(\vec{i},\vec{j},\vec{k};a_1,a_2,a_3;b_1,b_2,b_3)\ =(a_2b_3 - a_3,b_2)\vec{i}+(a_3b_1 - a_1,b_3)\vec{j}+(a_1b_2 - a_2,b_1)\vec{k}a×b=det(i,j ,k;a1 ,a2 ,a3 ;b1 ,b2 ,b3 )=(a2 b3 −a3 ,b2 )i+(a3 b1 −a1 ,b3 )j +(a1 b2 −a2 ,b1 )k
1.2 点积 Dot Product
叉积给出一个向量结果,但点积给出一个标量结果。
它将向量的相同方向投影的的长度相乘,因此使用cos θ \cos{\theta}cosθ将其中一个向量投影到另一个上。所以如果两个向量成直角,那么结果为零。点积更容易理解一些。
a ⃗ ⋅ b ⃗ = ∣ a ⃗ ∣ ∣ b ⃗ ∣ cos θ \vec{a}\cdot \vec{b} = |\vec{a}||\vec{b}|\cos{\theta}a⋅b=∣a∣∣b∣cosθ
2 实际应用
判断两个向量是否:
- 共线:A ⃗ \vec{A}A=k*B ⃗ \vec{B}B,其中k是一个标量;叉积是零向量(仅适用于三维空间);对应坐标的比率相等。
- 垂直:点积为零。
计算点P在线段AB上的投影点C坐标:
- 向量AB,AP,点积结果为D。AB ⃗ ⋅ AP ⃗ = D \vec{AB} \cdot \vec{AP}=DAB⋅AP=D
- 推导一下:AB ⃗ ⋅ AP ⃗ = ∣ AB ⃗ ∣ ∣ AP ⃗ ∣ cos θ = ∣ AC ⃗ ∣ ∣ AB ⃗ ∣ = D \vec{AB} \cdot \vec{AP}=|\vec{AB}| |\vec{AP}| \cos{\theta}=|\vec{AC}| |\vec{AB}| = DAB⋅AP=∣AB∣∣AP∣cosθ=∣AC∣∣AB∣=D->D / ∣ AB ⃗ ∣ = ∣ AC ⃗ ∣ D/|\vec{AB}| = |\vec{AC}|D/∣AB∣=∣AC∣
- 求比率:k = ∣ AC ⃗ ∣ / ∣ AB ⃗ ∣ = D / ∣ AB ⃗ ∣ 2 k = |\vec{AC}|/|\vec{AB}| = D/{|\vec{AB}|^2}k=∣AC∣/∣AB∣=D/∣AB∣2
- 最终坐标根据A和k可以求得:C = A + k AB ⃗ C = A + k\vec{AB}C=A+kAB
如何验证C是投影点:
- 验证其共线性:AC ⃗ = k AB ⃗ \vec{AC} = k \vec{AB}AC=kAB或AC ⃗ × AB ⃗ = 0 ⃗ \vec{AC} \times \vec{AB}=\vec{0}AC×AB=0
- 验证垂直:PC ⃗ ⋅ AB ⃗ = 0 \vec{PC}\cdot \vec{AB} = 0PC⋅AB=0
如何计算点P到线AB的距离d:
- 叉积的范数是由两个向量张成的平行四边形的面积( AB ⃗ × AP ⃗ ) (\vec{AB} \times \vec{AP})(AB×AP)
- 基于几何原理,这个面积也等于距离(高)乘以边长d ∗ ∣ AB ⃗ ∣ d * |\vec{AB}|d∗∣AB∣
- 所以d = ∣ AB ⃗ × AP ⃗ ∣ / ∣ AB ⃗ ∣ d = |\vec{AB} \times \vec{AP}|/|\vec{AB}|d=∣AB×AP∣/∣AB∣
3 代码实现
第一个版本代码,不用额外的库,手搓一些Utility函数,透彻了解原理:
#include<iostream>
#include<cmath>
using namespace std;
struct Point
{
double x, y, z;
// Overloading the multiplication operator
Point operator*(double k) const
{
return {k*x, k*y, k*z};
}
Point operator+(Point A) const
{
return {A.x + x, A.y + y, A.z + z};
}
bool operator==(Point A) const
{
if (A.x == x and A.y == y and A.z == z)
{
return true;
}
else
{
return false;
}
}
};
double dotProduct(Point A, Point B)
{
return A.x * B.x + A.y * B.y + A.z * B.z;
}
Point crossProduct(Point A, Point B)
{
return {A.y * B.z - A.z * B.y,
A.x * B.z - A.z * B.x,
A.x * B.y - A.y * B.x};
}
float calcNorm(Point A)
{
return sqrt(A.x * A.x + A.y * A.y + A.z * A.z);
}
Point calcProjection(Point A, Point B, Point P)
{
Point AB = {B.x - A.x, B.y - A.y, B.z - A.z};
Point AP = {P.x - A.x, P.y - A.y, P.z - A.z};
double dot_product = dotProduct(AB, AP);
double k = dot_product / dotProduct(AB, AB);
Point C = A + (AB * k);
return C;
}
bool verifyProjection(Point A, Point B, Point P, Point C)
{
Point AC = {C.x - A.x, C.y - A.y, C.z - A.z};
Point AB = {B.x - A.x, B.y - A.y, B.z - A.z};
Point PC = {C.x - P.x, C.y - P.y, C.z - P.z};
double dot_product = dotProduct(PC, AB);
Point cross_product = crossProduct(AC, AB);
Point zero_vec = {0, 0, 0};
if (dot_product == 0 and cross_product == zero_vec)
{
return true;
}
else
{
return false;
}
}
float calcDistance(Point A, Point B, Point P)
{
Point AB = {B.x - A.x, B.y - A.y, B.z - A.z};
Point AP = {P.x - A.x, P.y - A.y, P.z - A.z};
Point cross_product = crossProduct(AB, AP);
float area_parallelogram = calcNorm(cross_product);
return (area_parallelogram / calcNorm(AB));
}
int main()
{
// Line segment AB
Point A = {0, 0, 0};
Point B = {4, 0, 0};
// Point P
Point P = {5, 8, 0};
// Project P to AB and get point C
Point C = calcProjection(A, B, P);
cout << "Projection Point C: (" << C.x << ", " << C.y << ", " << C.z << ")" << endl;
if (verifyProjection(A, B, P, C))
{
cout << "Correct!" << endl;
}
else
{
cout << "Incorrect." << endl;
}
cout << "Distance from P to AB is: " << calcDistance(A, B, P) << endl;
return 0;
}
另一个版本,通过使用Eigen库来避免自己写Utility函数,行数大大减少:
#include <iostream>
#include <Eigen/Dense>
using namespace std;
using namespace Eigen;
Vector3d calcProjection(Vector3d A, Vector3d B, Vector3d P)
{
Vector3d AB = B - A;
Vector3d AP = P - A;
float AC_norm = AB.dot(AP) / AB.norm();
Vector3d C = A + AC_norm / AB.norm() * AB;
return C;
}
bool verifyProjection(Vector3d A, Vector3d B, Vector3d P, Vector3d C)
{
Vector3d AB = B - A;
Vector3d PC = P - C;
Vector3d AC = C - A;
Vector3d zero_vec = {0, 0, 0};
Vector3d cross_product = AB.cross(AC);
float dot_product = PC.dot(AB);
if (dot_product == 0 and cross_product == zero_vec)
{
return true;
}
else
{
return false;
}
}
float calcDistance(const Vector3d A, const Vector3d B, const Vector3d P)
{
Vector3d AB = B - A;
Vector3d AP = P - A;
Vector3d cross_product = AB.cross(AP);
float area_parallelogram = cross_product.norm();
return area_parallelogram / AB.norm();
}
int main()
{
Eigen::Vector3d A = {0, 0, 0};
Eigen::Vector3d B = {2, 0, 0};
Eigen::Vector3d P = {1, 3, 0};
Vector3d C = calcProjection(A, B, P);
cout << "Projection point is: " << C.x() << ", " << C.y() << ", " << C.z() << endl;
if (verifyProjection(A, B, P, C))
{
cout << "Verification passes!" << endl;
}
else
{
cout << "Verification failed." << endl;
}
cout << "Distance from P to AB is: " << calcDistance(A, B, P) << endl;
}