旋转示意:
3x3矩阵可以用于表示一个物体的旋转信息,例如下面的图形,下面三维图形没有做任何的平移旋转操作。途中红绿蓝三个箭头的方向分别代表X轴,Y轴,Z轴,并且,三轴的交点是原点(0,0,0)。
每个三维模型都是由大量的点面组成,有点就一定有一个坐标系,这个坐标系就是数据坐标系。当显示一个模型在空间不做任何移动,旋转,那么他的数据坐标系就刚好和显示空间的世界坐标系重合,如上图一样。上图的显示可以看出飞机模型原始数据的原点和方向(x',y',z'),就是和目前显示的完全一样,图中X,Y,Z和x',y',z'重合。当移动模型的时候,如下图:
上图的移动过程,将模型移动到了【18.0,36.0,54.0】的位置了,移动可以看作是眼三维向量的移动。与移动相对应的旋转也一样,旋转可以用3x3的矩阵表示。旋转示意如下,绕X轴旋转30度效果:
旋转前后,位置不变,模型的姿态(数据坐标姿态)和原始相比,绕X轴旋转了30度。在旋转前,数据坐标与世界坐标完全重合,此时,旋转矩阵为单位矩阵,当旋转30度后,旋转矩阵的X轴不发生变化,Y、Z轴均旋转了30度,矩阵分别如下:
注意,这里的矩阵是4x4的矩阵,代表了物体的旋转平移放大等信息,其旋转部分为左上角的3x3矩阵。如果我们先绕X轴旋转30度,再移动到【18.0,36.0,54.0】和我们先移动到【18.0,36.0,54.0】再绕X轴旋转30度,这两种情况如下图:
可以看出,两种操作得到的结构并不一样。
Matrix3x3:
旋转矩阵就是用于表示物体的旋转姿态信息,也包含放大信息,因此,在qySpace的3x3矩阵库的接口也是根据这样的需求来的,并不一定可以很好地适用于线性代数的计算。
用Matrix3x3来表示物体的旋转非常直观,一眼便可看出旋转后的X、Y、Z轴的方向。不仅如此,3x3矩阵还可以很好地表示出放大缩小信息,避免了使用欧拉角带来的万向节锁死问题。
3x3矩阵的构造函数和析构函数如下:
/// constructor, unit matrix default.
matrix3x3();
/// construct from another matrix3x3.
matrix3x3(const matrix3x3& mat);
/// move constructor.
matrix3x3(matrix3x3&& mat) noexcept;
/// construct from quaternion.
matrix3x3(const quaternion& q);
/// construct from pointer, there must be nine values.
matrix3x3(const real* data);
/// destructor.
virtual ~matrix3x3();
它可以直接构造、从另一个矩阵构造,从四元数构造,从数据指针构造。注意,上面的real数据实际上就是double。在vtk等三维引擎中,直接使用指针的地方很多,里面的矩阵一般也能够通过指针构造,因此,最后一个构造函数是为了兼容和使用方便设计的。
除此之外,矩阵计算还包括:
public:
/// return true if not equal.
bool operator!=(const matrix3x3& mat) const;
/// return true if equal, all values equal.
bool operator==(const matrix3x3& mat) const;
/// operator=, from another matrix3x3.
matrix3x3& operator=(const matrix3x3& mat);
/// move operator=.
matrix3x3& operator=(matrix3x3&& mat) noexcept;
/// operator= from quaternion.
matrix3x3& operator=(const quaternion& q);
/// operator=, from pointer, there must be nine values.
matrix3x3& operator=(const real* data);
/// operator*=, return *this, *this *= mat.
matrix3x3& operator*=(const matrix3x3& mat);
/// operator*, return *this * mat, non change in *this.
[[nodiscard]] matrix3x3 operator*(const matrix3x3& mat) const;
/// operator*, return *this * pt, non change in *this.
[[nodiscard]] point operator*(const point& pt) const;
/// operator*, return *this * vec, non change in *this.
[[nodiscard]] vector operator*(const vector& vec) const;
public:
/// print on console.
void print() const;
/// is real, is available.
bool isReal();
/// set matrix form x and y axis, calculate z axis automatically.
void rotateFromXYAxis(const vector& vecX, const vector& vecY);
/// set matrix from z axis, x and y are random, normalized vector.
void rotateFromZAxis(const vector& vecZ);
/// zero all data.
void zero();
/// convert to unit matrix.
void identity();
/// convert to invert matrix.
void invert();
/// convert to transpose matrix.
void transpose();
/// *this *= mat, save in this.
void multiplied(const matrix3x3& mat);
/// mat *= *this, save in this.
void preMultiplied(const matrix3x3& mat);
/// return *this * mat, non change in *this.
[[nodiscard]] matrix3x3 multiply(const matrix3x3& mat) const;
/// return mat * *this, non change in *this.
[[nodiscard]] matrix3x3 preMultiply(const matrix3x3& mat) const;
/// return *this * pt, non change in *this.
[[nodiscard]] point multiply(const point& pt) const;
/// return *this * vec, non change in *this.
[[nodiscard]] vector multiply(const vector& vec) const;
/// retuan determinate.
real determinate() const;
/// rotate around axis
void rotateAxis(const real radians, const vector& vec);
/// rotate around axis
void rotateAxis(const real radians, Axis axis);
/// rotate around self axis
void rotateSelfAxis(const real radians, Axis axis);
注释和函数名都能够非常好地表示函数实际功能。如果不明白也没关系,下面对Matrix3x3的每个函数进行测试和介绍。
测试:
一、构造函数和析构函数部分:
qytk::matrix3x3 mat0;
mat0.print();
qytk::matrix3x3 mat1(mat0);
mat1.print();
double d[9]{ 0,1,0,0,0,1,1,0,0 };
qytk::matrix3x3 mat2(d);
mat2.print();
其中前两个是构造的单位矩阵,第三个实际上是单位矩阵绕【1.0,1.0,1.0】轴旋转-120度的结果,可以脑补下这个旋转得到的应该是什么样的状态,其打印结果如下:
绕【1.0,1.0,1.0】旋转-120度,实际上是X轴旋转到Z轴,Y轴旋转到X轴,Z轴旋转到Y轴。如上第三个打印,第一列数据就代表X轴,为【0,0,1】,即变换后的X轴位于世界坐标的Z轴,以此类推。
二、对于operator!=和operator==,以及operator=不着重介绍,对于operator*如下:
mat0 *= mat3;
mat0.print();
mat3 = mat3 * mat3;
mat3.print();
qytk::point pt0(1.0, 2.0, 3.0);
qytk::point pt1 = mat3 * pt0;
pt1.print();
qytk::vector vec0(1.0, 2.0, 3.0);
qytk::vector vec1 = mat3 * vec0;
vec1.print();
初始状态为:mat0为单位矩阵,mat3为绕【1.0,1.0,1.0】旋转-120度的旋转矩阵。上面计算结果打印如下:
上面即为普通的矩阵计算方法,但其对空间变换的含义为:
1、将mat3的姿态应用一次单位矩阵的变换(实际就是什么都没变)
2、将mat3的姿态在应用一次mat3的变换(即绕【1.0,1.0,1.0】旋转-120度,再次绕这个轴旋转-120度,此时XYZ
轴的位置分别为:X->Y,Y->Z,Z->X)
3、点【1.0,2.0,3.0】经过mat3的变换后,得到新的点【3.0,1.0,2.0】,注意,新的点是在世界坐标系(单位矩阵)下面的坐标,这个计算过程可以看作点经过mat3变换到新的位置。
4、向量【1.0,2.0,3.0】经过mat3的变换后,得到的新的向量【3.0,1.0,2.0】,与上面点完全一样,此计算过程是将向量尖端变换到新的位置(变换后为世界坐标系下原点指向新的位置的向量)。
三、设置旋转矩阵
vec0.set(3, 4, 5);
vec1.set(-4, 3, 5);
mat4.identity();
mat4.rotateFromXYAxis(vec0, vec1);
mat4.print();
qytk::matrix3x3 mat5;
mat5.rotateFromZAxis(vec1);
mat5.print();
上面计算是根据设置XY两轴生成旋转矩阵、根据设置的Z轴生成旋转矩阵。其中根据Z轴生成的旋转矩阵的XY两轴是随机的,此接口是为了方便使用。而根据XY轴可以生成坐标系是建立在使用右手直角坐标系的基础上的。这两种建立旋转矩阵的方法都不存在缩放信息,只包含姿态信息。打印结果如下:
四、下面对普通的计算函数进行简要介绍,不做测试解析:
1、zero()、将矩阵置零,所有元素均赋值为0,再空间变换中,几乎不会使用这个函数。
2、identity()、将矩阵赋值为单位矩阵,一般用于初始化/复位矩阵。
3、invert()、求当前矩阵的逆矩阵,并赋值给当前矩阵。逆矩阵用于表示反向变换,及逆变换。
4、transpose()、求转置矩阵,一般不会使用。
5、determinate()、计算矩阵的行列式值并返回,一般外部不使用,在内部求取逆矩阵时会使用。
五、矩阵乘法:
void multiplied(const matrix3x3& mat);
/// mat *= *this, save in this.
void preMultiplied(const matrix3x3& mat);
/// return *this * mat, non change in *this.
matrix3x3 multiply(const matrix3x3& mat) const;
/// return mat * *this, non change in *this.
matrix3x3 preMultiply(const matrix3x3& mat) const;
/// return *this * pt, non change in *this.
point multiply(const point& pt) const;
/// return *this * vec, non change in *this.
vector multiply(const vector& vec) const;
矩阵乘法部分与上面的operator*和operator*=完全一样,示例与上面也基本一样,不介绍。
六、旋转部分:
/// rotate around axis
void rotateAxis(const real radians, const vector& vec);
/// rotate around axis
void rotateAxis(const real radians, Axis axis);
/// rotate around self axis
void rotateSelfAxis(const real radians, Axis axis);
这部分是矩阵旋转,前两个函数是矩阵绕指定轴旋转,最后一个是矩阵绕当前自身轴旋转(XYZ轴或-X-Y-Z轴之一),,示例如下:
qytk::matrix3x3 mat8;
mat8.rotateAxis(qytk::radiansFromDegrees(120), qytk::vector(1.0, 1.0, 1.0));
mat8.print();
mat8.identity();
mat8.rotateAxis(qytk::radiansFromDegrees(30.0), qytk::Axis::AXIS_X);
mat8.print();
mat8.rotateSelfAxis(qytk::radiansFromDegrees(30.0), qytk::Axis::AXIS_Y);
mat8.print();
打印如下:
效果分别如下:
总结:
3x3的矩阵可用于表示三维空间的缩放、旋转姿态等信息,同时,四元数也可以表示三维空间的旋转信息,可以将四元数转换成3x3矩阵。