3D数学基础:Matrix4×3类重点代码解析(一)
Matrix4×3矩阵和RotationMatrix矩阵是《3D数学基础》中的两大矩阵。相比于只能运用于旋转功能的RotationMatrix矩阵,Matrix 4×3矩阵更加一般化,能够支持更加复杂的变换。
该矩阵类及其实现的代码清单相对较长(完整的代码可以参看代码清单),不便于快速获取较重要的信息,下面对常用的API进行解析。
一、公共数据解析
该类描述的4*3的矩阵,上面3×3部分包含线性变换信息,最后一行包含平移信息。具体的形式如下:
float m11, m12, m13;
float m21, m22, m23;
float m31, m32, m33;
float tx, ty, tz;
此处简单说明一下3*3部分包含线性变换的含义:在数学上满足F(a+b)=F(a)+F(b)
和F(kb)=kF(b)
的就是线性变换,即保持假发和数量乘运算就是线性的。同时,由该定义可以引申出下面三条引理(经常被使用)。
①当变换M是一个任意的方阵(行数目=列数目)时,映射F(a)=aM
是一个线性变换。可以验证由于矩阵运算满足结合律和分配律,该映射满足上述线性的定义。那么旋转、缩放、投影和镜像都是线性变换。
②线性变换不会导致平移。此处不证明,希望深入了解的可以参考《3D数学基础》第8.8节的内容。
看完线性变换的定义和引理之后,相应应该明白为什么3×3部分能够包含线性变换信息,3×3部分刚好是一个方阵,可以是包含旋转、缩放、投影和镜像等信息。
二、常用函数解析
Matrix4×3类中的函数众多,但是可以大致分为以下几个部分,下面每个类型可能同时包含多个函数,但是实现的功能都是属于同一个大类别的。从下表可以看出,前面一部分主要是构造满足某一功能的矩阵,后面主要是涉及设置类的参数值或者计算类的参数值。
类型 | 描述 |
---|---|
*构造父<–>局部变换矩阵 | 父→局部空间的变换矩阵;局部→父空间的变换矩阵 |
构造旋转矩阵 | 绕坐标轴旋转;绕过原点的任意轴旋转 |
构造缩放矩阵 | 沿各坐标轴缩放的矩阵;沿任意轴缩放的矩阵 |
构造切边矩阵 | 任意类型的切边矩阵 |
构造投影矩阵 | 投影平面过原点且垂直于单位向量 |
构*射矩阵 | 反射平面平行于坐标平面;反射平面为任意过原点的平面且垂直于单位向量 |
值设定 | 设定3×3部分矩阵;设定平移量 |
值提取 | 返回3×3部分的行列式;返回平移向量 |
*物体位置提取 | 从父→局部变换矩阵中提取物体的位置;从局部→父变换矩阵中提取物体的位置 |
第一个和最后一个都是涉及到父空间和局部空间变换矩阵的函数,将作为本篇文章的重点进行讲述和分析。
(一)父空间<—>局部空间变换矩阵的构造
// 构造一个矩阵来表现一个从父空间到局部空间或相反方向的特定的转换,
// 假定本地空间在父空间的具体指定位置和朝向。
//并且这个朝向可能是被欧拉角或者旋转矩阵来指定的。
// Setup the matrix to perform a specific transforms from parent <->
// local space, assuming the local space is in the specified position
// and orientation within the parent space. The orientation may be
// specified using either Euler angles, or a rotation matrix
void setupLocalToParent(const Vector3 &pos, const EulerAngles &orient);
void setupLocalToParent(const Vector3 &pos, const RotationMatrix &orient);
void setupParentToLocal(const Vector3 &pos, const EulerAngles &orient);
void setupParentToLocal(const Vector3 &pos, const RotationMatrix &orient);
从上面函数声明来看,大致可以划分为两个部分,从父坐标系到局部坐标系;从局部坐标系到父坐标系。下面分别来看。
1.局部→父空间变换矩阵的构造
Matrix4×3::setupLocalToParent
//构造用来执行”局部→父空间变换”的矩阵
//---------------------------------------------------------------------------
// Matrix4x3::setupLocalToParent
//
// 给定一个父参考帧中的局部参考帧的位置和朝向,构造矩阵来执行从局部空间->父空间的转换。
// Setup the matrix to perform a local -> parent transformation, given
// the position and orientation of the local reference frame within the
// parent reference frame.
//
// 一个非常常见的用法是构造从物体->世界矩阵。
// 作为一个示例,这种情况的转换是很直接的。
// 我们首先从物体坐标系到惯性坐标系的转换,然后平移到世界空间。
// A very common use of this will be to construct a object -> world matrix.
// As an example, the transformation in this case is straightforward. We
// first rotate from object space into inertial space, then we translate
// into world space.
//
// 我们允许通过欧拉角或者旋转矩阵来指定方位。
// We allow the orientation to be specified using either euler angles,
// or a RotationMatrix
该方法最常见的用途就是构造物体坐标系→世界坐标系的变换矩阵,首先从物体空间变换到惯性空间,接着变换到世界空间。你可能很疑惑到底怎么构造,需要输入哪些参数。具体来说,应该是给定一个父参考帧中的局部参考帧的位置(Vector3 &pos
)和朝向(EulerAngles &orient / RotationMatrix &orient
),构造矩阵来执行从局部空间->父空间的转换。
void Matrix4x3::setupLocalToParent(const Vector3 &pos, const EulerAngles &orient) {
// 通过参数orient创建旋转矩阵
// Create a rotation matrix.
RotationMatrix orientMatrix;
orientMatrix.setup(orient);
//获得欧拉角对应的矩阵之后,调用另一个局部→父空间的函数。
setupLocalToParent(pos, orientMatrix);
}
局部空间的朝向可以使用欧拉角表示,也可以用旋转矩阵表示。因而有上下两个函数,第一个以欧拉角为参数。在使用矩阵时,要注意RotationMatrix &orient
必须是物体坐标系->惯性坐标系的矩阵,如果是惯性坐标系->物体坐标系的需要做转置才能作为传入参数使用。
void Matrix4x3::setupLocalToParent(const Vector3 &pos, const RotationMatrix &orient) {
// 复制矩阵的旋转部分。根据RotationMatrix.cpp的注释,这个旋转矩阵通常是惯性坐标系->物体坐标系的矩阵,
// 也就是从父空间->局部空间。我们想要局部空间->父空间旋转,复制的时候必须要转置。
// Copy the rotation portion of the matrix. According to
// the comments in RotationMatrix.cpp, the rotation matrix
// is "normally" an inertial->object matrix, which is
// parent->local. We want a local->parent rotation, so we
// must transpose while copying
m11 = orient.m11; m12 = orient.m21; m13 = orient.m31;
m21 = orient.m12; m22 = orient.m22; m23 = orient.m32;
m31 = orient.m13; m32 = orient.m23; m33 = orient.m33;
// 现在设置平移部分。平移发生在3x3部分之后,所以我们可以简单直接地复制位置。
// Now set the translation portion. Translation happens "after"
// the 3x3 portion, so we can simply copy the position
// field directly
tx = pos.x; ty = pos.y; tz = pos.z;
}
2.父→局部变换矩阵的构造
Matrix4×3::setupParentToLocal
//构造用来执行”父→局部变换”的矩阵
该方法常见的用途是构造世界→物体的变换矩阵,与上面的恰好相反,但两者的具体实现之间还是有差别,一定要注意。首先使用的矩阵参数此时要求是从惯性坐标系→物体坐标系,与局部→父空间转换中用到的矩阵恰恰相反。
//---------------------------------------------------------------------------
// Matrix4x3::setupParentToLocal
//
// 给定在父空间参考帧下的局部坐标的位置和方向参考帧,构造矩阵来执行父空间->局部空间。
// Setup the matrix to perform a parent -> local transformation, given
// the position and orientation of the local reference frame within the
// parent reference frame.
//
// 它的一个非常常见的用法就是从世界坐标系->物体坐标系。
// 为了执行这个转换,我们通常首先会从世界坐标系到惯性坐标系的转换,然后从惯性坐标系到物体坐标系的旋转。
// 然而4x3矩阵可以完成后一个转换。所以我们可以创建两个矩阵T和R,然后连接M = TR。
// A very common use of this will be to construct a world -> object matrix.
// To perform this transformation, we would normally FIRST transform
// from world to inertial space, and then rotate from inertial space into
// object space. However, out 4x3 matrix always translates last. So
// we think about creating two matrices T and R, and then concatonating
// M = TR.
//
// 我们允许使用欧拉角或者旋转矩阵来指定朝向。
// We allow the orientation to be specified using either euler angles,
// or a RotationMatrix
下面首先是通过位置(Vector3 &pos
)和欧拉角(EulerAngles &orient
)构造从父→局部转换的矩阵。
void Matrix4x3::setupParentToLocal(const Vector3 &pos, const EulerAngles &orient) {
// 创建一个旋转矩阵。
// Create a rotation matrix.
RotationMatrix orientMatrix;
orientMatrix.setup(orient);
// 构造4x3矩阵。
// Setup the 4x3 matrix.
setupParentToLocal(pos, orientMatrix);
}
下面首先是通过位置(Vector3 &pos
)和旋转矩阵(RotationMatrix &orient
)构造从父→局部转换的矩阵。
void Matrix4x3::setupParentToLocal(const Vector3 &pos, const RotationMatrix &orient) {
// 复制矩阵的旋转部分。我们根据RotationMatrix.cpp注释中的布局,可以直接复制元素(不用转置)
// Copy the rotation portion of the matrix. We can copy the
// elements directly (without transposing) according
// to the layout as commented in RotationMatrix.cpp
m11 = orient.m11; m12 = orient.m12; m13 = orient.m13;
m21 = orient.m21; m22 = orient.m22; m23 = orient.m23;
m31 = orient.m31; m32 = orient.m32; m33 = orient.m33;
// 现在设置平移部分。通常地,我们通过取位置的负号来实现从世界到惯性坐标系。
// 然而,我们必须修正这个事实——旋转是先发生的。所以必须先旋转平移部分。
// 这和创建一个平移矩阵T来平移-pos,和旋转矩阵R,并且创建一个连接矩阵TR是一样的。
// Now set the translation portion. Normally, we would
// translate by the negative of the position to translate
// from world to inertial space. However, we must correct
// for the fact that the rotation occurs "first." So we
// must rotate the translation portion. This is the same
// as create a translation matrix T to translate by -pos,
// and a rotation matrix R, and then creating the matrix
// as the concatenation of TR
tx = -(pos.x*m11 + pos.y*m21 + pos.z*m31);
ty = -(pos.x*m12 + pos.y*m22 + pos.z*m32);
tz = -(pos.x*m13 + pos.y*m23 + pos.z*m33);
}
简单解析一下为什么需要旋转平移。该函数获得了一个从父→局部转换的矩阵M,某一个需要转换的向量A(在父空间下的描述)与该矩阵M相乘获得A局部坐标系下的表示A*M。其实M是由矩阵矩阵T和旋转矩阵R组成的,并且旋转是”先”发生的,所以旋转部分也应该进行平移。(没讲清楚?有大佬可以继续深入讲解)
(二)变换矩阵中提取物体的位置
1.从局部→父(如物体→世界)变换矩阵中提取物体的位置
在已知局部→父空间转换矩阵的条件下,提取物体的位置相对简单。矩阵最后一行的平移部分就是物体位置。
//---------------------------------------------------------------------------
// getPositionFromLocalToParentMatrix
//
// 提取给定局部坐标系->父坐标系的位置变换矩阵(例如物体坐标系->世界坐标系矩阵)
// Extract the position of an object given a local -> parent transformation
// matrix (such as an object -> world matrix)
Vector3 getPositionFromLocalToParentMatrix(const Matrix4x3 &m) {
// 位置简明地是平移部分
// Position is simply the translation portion
return Vector3(m.tx, m.ty, m.tz);
}
2.从父→局部(如世界→物体)变换矩阵中提取物体的位置
在已知父→局部变换矩阵时,该函数用来从矩阵中提取物体的位置。强调一下。此时要求该变换矩阵完成的是刚体变换,即没有缩放和镜像等。
最后,想必也发现为什么从局部→父空间的转换矩阵的平移部分就是位置,而父→局部空间转换矩阵的位置还要经过一系列计算。这将在后面的文章中进行详细的解析。
//---------------------------------------------------------------------------
// getPositionFromParentToLocalMatrix
//
// 提取从父空间->局部空间变换矩阵的平移部分(就像世界坐标系->物体坐标系矩阵)
// Extract the position of an object given a parent -> local transformation
// matrix (such as a world -> object matrix)
//
// 我们假定矩阵呈现的是坚固的变换。(没有缩放、倾斜、或者镜像)
// We assume that the matrix represents a rigid transformation. (No scale,
// skew, or mirroring)
Vector3 getPositionFromParentToLocalMatrix(const Matrix4x3 &m) {
// 通过转置3x3部分乘以负平移值。通过矩阵的转置,我们假定矩阵是正交的。
//(这个函数对于非坚固变换的变换是没有意义的)
// Multiply negative translation value by the
// transpose of the 3x3 portion. By using the transpose,
// we assume that the matrix is orthogonal. (This function
// doesn't really make sense for non-rigid transformations...)
return Vector3(
-(m.tx*m.m11 + m.ty*m.m12 + m.tz*m.m13),
-(m.tx*m.m21 + m.ty*m.m22 + m.tz*m.m23),
-(m.tx*m.m31 + m.ty*m.m32 + m.tz*m.m33)
);
}
上一篇: 爬取表情包
下一篇: 基于XML配置的SpringIOC案例