7.1节描述了二维极坐标。
7.2节给出了一些极坐标比笛卡尔坐标更可取的例子。
7.3节展示了极空间如何在三维中工作,并介绍了圆柱坐标和球面坐标。
最后,7.4节明确指出,极坐标空间既可以用来描述位置,也可以用来描述矢量。
7.1 关于二维极坐标空间
极坐标空间只有一个轴(极轴),它通常被描述为来自原点的射线。在数学文献中,极轴通常在图表中指向右,因此在笛卡尔坐标系中,它对应于+x轴。
使用二维极坐标定位一个点(r, θ)
- 步骤1。从原点开始,面向极轴的方向,旋转θ角。θ的正值通常解释为平均逆时针旋转,负值则解释为顺时针旋转。
- 步骤2。现在从原点向前移动r个单位的距离。到达了极坐标(r, θ)所描述的点。
r定义了点到原点的距离,θ定义了点到原点的方向。
注意:我们喜欢用度数表示角度,但是计算机更喜欢使用弧度来表示角度。
对于任何给定的点,有无穷多个极坐标对可以用来描述这个点。这种现象被称为别名( Aliasing)。如果两个坐标对的数值不同,但指向空间中的同一点,则它们被称为彼此的别名。注意,在笛卡尔空间中不会出现别名——空间中的每个点都被分配了一个(x, y)坐标对:点到坐标对的映射是一对一的。
一般来说,对于除原点以外的任何一点(r, θ),所有作为(r, θ)别名的极坐标都可以表示为(其中k是任意整数):
(
(
−
1
)
k
r
,
θ
+
180
°
)
((-1)^kr, θ+180°)
((−1)kr,θ+180°)
一个点的最佳极坐标描述:
- r >= 0:我们不“向后”测量距离。
- −180° < θ ≤ 180度:角度限制在1/2圈,使用+180°表示朝“西”。
- r = 0 => θ = 0。在原点,把角度设为零。
将极坐标对(r, θ)转换为标准形式
- 如果r = 0,则赋值θ = 0。
- 如果r < 0,则r为负,并向θ加180°。
- 如果θ≤−180°,则将θ加360°,直到θ >−180°。
- 如果θ > 180°,则将θ减360°,直到θ≤180°。
将极坐标对(r, θ)转换为标准形式C代码:
/**
*@r: 径向距离
*@theta: 弧度为单位的角度
*/
void canonical(float &r, float &theta) {
//声明一个2 * pi (360度) 的常量
static float TWOPI = 2.0f * PI;
//检查我们是否正好在原点
if(r == 0.0f) {
//在原点,则强制theta为零
theta = 0.0f;
} else {
//处理负距离
if(r < 0.0f) {
r = -r;
theta += PI;
}
/*
theta是否超出范围了?
注意,这个if()检查不是严格必要的,
但如果浮点运算不是必需的,我们会尽量避免它们。
如果不需要,为什么要损失浮点精度呢?
*/
if(fabs(theta) > PI) {
//按 PI 值弥补
theta += PI;
//包含在范围 [0, TWOPI]
theta -= floor(theta / TWOPI) * TWOPI;
//撤消弥补,将角度转换回范围(-PI, PI]
theta -= PI;
}
}
}
直角坐标和极坐标之间的转换
将二维极坐标转换为笛卡尔坐标
二维笛卡尔坐标到极坐标的转换
二维笛卡尔坐标到极坐标的转换C代码:
/**
*@x: 笛卡尔坐标x
*@y: 笛卡尔坐标y
*@r: 径向距离
*@theta: 弧度为单位的角度
*/
void conversion(float x, float y, float &r, float &theta) {
//检查是否在原点
if (x == 0.0f && y == 0.0f){
//在原点,两个极坐标都为零
r = 0.0f;
theta = 0.0f;
}else {
//计算值。atan2函数是不是很棒吧?
r = sqrt(x * x + y * y);
theta = atan2(y, x);
}
}
7.2 为什么有人会使用极坐标?
极坐标的出现是因为人们很自然地用距离和方向来考虑位置。(当然,在使用极坐标时,我们通常不是很精确,但精确并不是大脑的强项。)笛卡尔坐标不是我们的母语。计算机的情况正好相反——一般来说,当使用计算机解决几何问题时,使用笛卡尔坐标比使用极坐标更容易。
7.3 关于三维极坐标空间
圆柱坐标(一角两长度)
在二维极坐标的基础上添加z轴
球面坐标(两角一长度)
在三维球面空间中也有两个极轴。第一个轴是“水平的”,对应于2D极坐标中的极轴或3D笛卡尔坐标中的+x轴。另一个轴是垂直的,对应于三维笛卡尔坐标中的+y。
水平角θ称为方位角(azimuth),φ为天顶(zenith)。
你可能听说过的其他术语还有经度和纬度。经度和θ基本相同,纬度是倾斜角90°−φ。
在三维虚拟世界中有用的一些极坐标约定
- 默认水平方向在θ = 0点方向+x。这很不幸,因为对我们来说,+x点“向右”或者“东方”,这两个方向都不是大多数人心目中的“默认”方向。类似于时钟上的数字从顶部开始的方式,如果水平极轴指向+z,即“向前”或“向北”,对我们来说会更好。
- 关于φ的惯例在几个方面都是不幸的。如果二维极坐标(r, θ)能简单地通过添加第三个零坐标扩展到三维就更好了,就像我们把笛卡尔坐标系从二维扩展到三维一样。但是球面坐标(r, θ, 0)与我们想要的二维极坐标(r, θ)并不对应。事实上,赋值φ = 0会使我们陷入万向节死锁(Gimbal lock)的尴尬境地(奇点)。相反,二维平面上的点表示为(r, θ, 90°)。测量纬度可能比测量天顶更直观。大多数人会将默认值认为是水平的,而向上是极端的情况。
如果球面坐标的两个角和欧拉角(第8章)的前两个角一样就好了,本书描述一些更适合我们的目的的球面坐标:
水平角θ改名为h, h是航向(Heading)的缩写,类似于指南针的航向。零表示“向前”或“向北”的方向,这取决于上下文。方向为0对应于三维笛卡尔坐标的+z。此外,由于本书用的是左手坐标系,从上面看,正旋转将顺时针旋转。
对顶角φ被重新命名为p,它是俯仰(pitch)的缩写,用来衡量我们向上或向下看的程度。默认的俯仰值0表示水平方向,这是我们大多数人直觉上期望的。也许不那么直观,正的俯仰值是向下旋转的,这意味着俯仰值实际上测量了偏斜角(angle of declination)。
球面坐标也有别名现象(坐标系包含角度都有别名现象)
二维极空间中的奇点出现在原点,因为当r = 0时,角坐标无关紧要。在球面坐标系下,两个角度在原点都是无关的。
当俯仰角设置为±90°时(或这些值的任何别名),出现奇点。在这种情况下,即所谓的万向节死锁(Gimbal lock),所指示的方向是纯垂直的(直上或直下),而方向角是无关紧要的。
ps:第一次看见万向节死锁把它看成了万圣节死锁(Halloween lock)૧(●´৺`●)૭
规范球面坐标所满足的条件
- r >= 0:我们不“向后”测量距离。
- −180° < h ≤ 180°:航行限制为1/2圈。使用+180°表示朝“南”。
- −90° ≤ p ≤ 90°:俯仰限制为直上直下,不能“向后”俯仰。
- r = 0 => h = p = 0:在原点,把角度设为零。
- |p| = 90° => h = 0:当我们直接向上或向下看的时候,我们把航向设为零。
将球面坐标(r, h, p)转换为标准形式
- 如果r = 0,那么赋值h = p = 0。
- 如果r < 0,则r变负,h加上180°, 并使p变负。
- p < 90°,则对p加360°,直到p ≥ 90°。
- 如果p > 270°,则从p减去360°,直到p ≤ 270°。
- 如果p > 90°, h加180°,设p = 180°−p。
- h≤−180°,则对h加360°,直至h > −180°。
- 如果h > 180°,则从h减去360°,直到h ≤ 180°。
将球面坐标(r, h, p)转换为标准形式C代码:
/**
*@r: 径向距离
*@heading: 弧度为单位的角度
*@pitch: 弧度为单位的角度
*/
void canonical(float &r, float &heading, float &pitch) {
//声明一个2 * pi (360度) 的常量
static float TWOPI = 2.0f * PI;
//声明一个pi / 2 (90度) 的常量
static float PIOVERTWO = PI / 2.0f;
//检查我们是否正好在原点
if(r == 0.0f) {
//在原点,则强制角度为零
heading = pitch = 0.0f;
} else {
//处理负距离
if(r < 0.0f) {
r = -r;
theta += PI;
pitch = -pitch;
}
//俯仰是否超出了范围?
if (fabs(pitch) > PIOVERTWO){
//按 PIOVERTWO 值弥补
pitch += PIOVERTWO;
//包含在范围 [0, TWOPI]
pitch -= floor(pitch / TWOPI) * TWOPI;
//是否超出了范围?
if (pitch > PI){
//翻转航行
heading += PI;
//撤消弥补,并设置pitch = 180 - pitch
pitch = 3.0f * PI / 2.0f - pitch; //p = 270度 - p
}else{
//撤消弥补,将角度转换回范围(-PIOVERTWO, PIOVERTWO]
pitch -= PIOVERTWO;
}
}
/*
是否为万向节死锁?
在这里使用相对较小的公差进行测试,接近单一精度的极限。
*/
if(fabs(heading) > PI) {
//按 PI 值弥补
heading += PI;
//包含在范围 [0, TWOPI]
heading -= floor(heading / TWOPI) ? TWOPI;
//撤消弥补,将角度转换回范围(-PI, PI]
heading -= PI;
}
}
}
将数学爱好者使用的球面坐标转换为三维笛卡儿坐标
本书中使用的规则的球面坐标到三维笛卡尔坐标的转换
三维笛卡尔坐标到本书中使用的规则的球面坐标的转换
原点处的奇点,即r = 0,被当作一种特殊情况处理。
三维笛卡尔坐标到本书中使用的规则的球面坐标的转换C代码:
/**
*@x: 笛卡尔坐标x
*@y: 笛卡尔坐标y
*@z: 笛卡尔坐标z
*@r: 径向距离
*@heading: 弧度为单位的角度
*@picth: 弧度为单位的角度
*/
void conversion(float x, float y, float z, float &r, float &heading, float &picth) {
//声明一个2 * pi (360度) 的常量
static float TWOPI = 2.0f * PI;
//声明一个pi / 2 (90度) 的常量
static float PIOVERTWO = PI / 2.0f;
//计算径向距离
r = sqrt(x * x + y * y + z * z);
//检查是否在原点
if (r == 0.0f){
//在原点,两个极坐标都为零
heading = picth = 0.0f;
}else {
//计算俯仰角
picth = asin(-y / r);
/*
检查是否万向节死锁,
因为atan2库函数在(二维)原点处未定义
在这里使用相对较小的公差进行测试,接近单一精度的极限
*/
if (fabs(picth) >= PIOVERTWO * 0.9999){
heading = 0.0f;
} else {
heading = atan2(x, z);
}
}
}
7.4 使用极坐标指定矢量
对极坐标点的运算对于极坐标矢量同样有效。