3D数学中的笛卡尔坐标系详解及实例
1. 笛卡尔坐标系基础
1.1 定义与起源
笛卡尔坐标系是由法国数学家笛卡尔(René Descartes)提出的空间表示方法,是现代计算机图形学、游戏开发和其他3D应用的基础。在三维空间中,笛卡尔坐标系由三个相互垂直的坐标轴组成:
- X轴:通常表示水平方向
- Y轴:在2D中表示垂直方向;在3D中,根据使用的约定不同可表示垂直或深度方向
- Z轴:在3D中增加的第三个维度,通常表示深度
1.2 右手坐标系与左手坐标系
在3D笛卡尔坐标系中,有两种主要的约定:
右手坐标系:用右手握住Z轴,拇指指向Z轴正方向,食指指向X轴正方向,此时中指自然指向的方向为Y轴正方向。
左手坐标系:用左手握住Z轴,拇指指向Z轴正方向,食指指向X轴正方向,此时中指自然指向的方向为Y轴正方向。
cpp
// 坐标系类型枚举
enum CoordinateSystemType {
RIGHT_HANDED,
LEFT_HANDED
};
// 确定使用的坐标系类型
const CoordinateSystemType COORDINATE_SYSTEM = RIGHT_HANDED;
1.3 不同领域的坐标系约定
不同的领域和软件系统使用不同的坐标系约定:
- 计算机图形学/OpenGL:通常使用右手坐标系,Z轴指向屏幕外(远离观察者)
- DirectX:使用左手坐标系,Z轴指向屏幕内(朝向观察者)
- Unity3D:使用左手坐标系,Y轴向上,Z轴向前
- Unreal Engine:使用左手坐标系,Z轴向上,Y轴向前
- 航空航天:通常使用右手坐标系,Z轴向上,X轴向前
2. 点与向量
2.1 点与向量的区别
在3D数学中,点和向量是两个基本概念:
- 点(Point):表示空间中的一个位置,有具体的坐标值
- 向量(Vector):表示空间中的方向和大小,没有具体的位置
虽然它们在代码实现中可能使用相似的结构,但在概念和操作上有明显区别。
cpp
struct Point3D {
float x, y, z;
Point3D() : x(0.0f), y(0.0f), z(0.0f) {}
Point3D(float x, float y, float z) : x(x), y(y), z(z) {}
};
struct Vector3D {
float x, y, z;
Vector3D() : x(0.0f), y(0.0f), z(0.0f) {}
Vector3D(float x, float y, float z) : x(x), y(y), z(z) {}
// 向量可以有长度
float length() const {
return std::sqrt(x*x + y*y + z*z);
}
// 向量可以被归一化
Vector3D normalize() const {
float len = length();
if (len < 1e-6f) return Vector3D(0, 0, 0);
return Vector3D(x/len, y/len, z/len);
}
};
// 点与点之间的减法产生向量
Vector3D vectorFromPoints(const Point3D& from, const Point3D& to) {
return Vector3D(to.x - from.x, to.y - from.y, to.z - from.z);
}
// 点加向量得到新的点
Point3D translatePoint(const Point3D& point, const Vector3D& displacement) {
return Point3D(point.x + displacement.x,
point.y + displacement.y,
point.z + displacement.z);
}
2.2 向量操作
在3D数学中,向量有几个基本操作:
2.2.1 向量加法和减法
cpp
Vector3D operator+(const Vector3D& a, const Vector3D& b) {
return Vector3D(a.x + b.x, a.y + b.y, a.z + b.z);
}
Vector3D operator-(const Vector3D& a, const Vector3D& b) {
return Vector3D(a.x - b.x, a.y - b.y, a.z - b.z);
}
2.2.2 向量标量乘法
cpp
Vector3D operator*(const Vector3D& v, float scalar) {
return Vector3D(v.x * scalar, v.y * scalar, v.z * scalar);
}
Vector3D operator*(float scalar, const Vector3D& v) {
return v * scalar;
}
2.2.3 向量点积
点积(也称为内积)产生一个标量值,表示两个向量的"相似度"。
cpp
float dot(const Vector3D& a, const Vector3D& b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
点积的几个重要性质:
- 如果两个向量垂直,点积为0
- 如果两个向量方向相同,点积为正
- 如果两个向量方向相反,点积为负
- 公式:a·b = |a|·|b|·cos(θ),其中θ是两个向量之间的角度
2.2.4 向量叉积
叉积(也称为外积)产生一个新的向量,该向量垂直于原始两个向量所在的平面。
cpp
Vector3D cross(const Vector3D& a, const Vector3D& b) {
return Vector3D(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
);
}
叉积在右手坐标系和左手坐标系中的方向是相反的。在右手坐标系中,a×b的方向遵循右手法则。
2.2.5 求两个向量间的角度
cpp
float angleBetweenVectors(const Vector3D& a, const Vector3D& b) {
float dotProduct = dot(a, b);
float magnitudeA = a.length();
float magnitudeB = b.length();
// 防止除以零和浮点精度问题
if (magnitudeA < 1e-6f || magnitudeB < 1e-6f) {
return 0.0f;
}
float cosTheta = dotProduct / (magnitudeA * magnitudeB);
// 确保cosTheta在[-1, 1]范围内,避免因精度问题导致的acos域错误
cosTheta = std::max(-1.0f, std::min(1.0f, cosTheta));
return std::acos(cosTheta);
}
3. 坐标变换
3.1 坐标系之间的转换
在不同的坐标系之间转换坐标是常见操作。比如,从右手坐标系转换到左手坐标系,或从一种约定转换到另一种约定。
cpp
// 从右手坐标系(OpenGL风格)转换到左手坐标系(DirectX风格)
Point3D convertRightToLeftHanded(const Point3D& point) {
// 典型转换是翻转Z轴
return Point3D(point.x, point.y, -point.z);
}
// 从Unity坐标系(Y向上,Z向前)转换到Unreal坐标系(Z向上,Y向前)
Point3D convertUnityToUnreal(const Point3D& unityPoint) {
// Unity: X右,Y上,Z前
// Unreal: X前,Y右,Z上
return Point3D(unityPoint.z, unityPoint.x, unityPoint.y);
}
3.2 局部坐标系与世界坐标系
在3D应用中,通常会使用多个坐标系:
- 局部坐标系:相对于对象自身的坐标系
- 世界坐标系:全局参考坐标系
cpp