旋转示意:

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度效果:

空间权重矩阵和数据对应 空间权重矩阵类型_空间权重矩阵和数据对应_02

空间权重矩阵和数据对应 空间权重矩阵类型_空间计算_03

旋转前后,位置不变,模型的姿态(数据坐标姿态)和原始相比,绕X轴旋转了30度。在旋转前,数据坐标与世界坐标完全重合,此时,旋转矩阵为单位矩阵,当旋转30度后,旋转矩阵的X轴不发生变化,Y、Z轴均旋转了30度,矩阵分别如下:

空间权重矩阵和数据对应 空间权重矩阵类型_数据_04

  

空间权重矩阵和数据对应 空间权重矩阵类型_旋转矩阵_05

注意,这里的矩阵是4x4的矩阵,代表了物体的旋转平移放大等信息,其旋转部分为左上角的3x3矩阵。如果我们先绕X轴旋转30度,再移动到【18.0,36.0,54.0】和我们先移动到【18.0,36.0,54.0】再绕X轴旋转30度,这两种情况如下图:

空间权重矩阵和数据对应 空间权重矩阵类型_空间权重矩阵和数据对应_06

  

空间权重矩阵和数据对应 空间权重矩阵类型_空间权重矩阵和数据对应_07

可以看出,两种操作得到的结构并不一样。

 

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度的结果,可以脑补下这个旋转得到的应该是什么样的状态,其打印结果如下:

空间权重矩阵和数据对应 空间权重矩阵类型_旋转矩阵_08

  

空间权重矩阵和数据对应 空间权重矩阵类型_空间权重矩阵和数据对应_09

  

空间权重矩阵和数据对应 空间权重矩阵类型_空间计算_10

绕【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度的旋转矩阵。上面计算结果打印如下:

空间权重矩阵和数据对应 空间权重矩阵类型_空间计算_11

上面即为普通的矩阵计算方法,但其对空间变换的含义为:

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轴可以生成坐标系是建立在使用右手直角坐标系的基础上的。这两种建立旋转矩阵的方法都不存在缩放信息,只包含姿态信息。打印结果如下:

空间权重矩阵和数据对应 空间权重矩阵类型_数据_12

四、下面对普通的计算函数进行简要介绍,不做测试解析:

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();

打印如下:

空间权重矩阵和数据对应 空间权重矩阵类型_数据_13

效果分别如下:

空间权重矩阵和数据对应 空间权重矩阵类型_数据_14

空间权重矩阵和数据对应 空间权重矩阵类型_数据_15

空间权重矩阵和数据对应 空间权重矩阵类型_空间计算_16

 

总结:

3x3的矩阵可用于表示三维空间的缩放、旋转姿态等信息,同时,四元数也可以表示三维空间的旋转信息,可以将四元数转换成3x3矩阵。