【引擎开发技术点记录】RotationGizmo的纯C++实现方式

初衷

写这篇小文章,是发现网上对于C++实现的Gizmo实现的描述太少了,大多都是在讲Unity Gizmo怎么使用。而我最近在研究开发自己的图形引擎,Gizmo的实现成为了我的一个障碍。研究一下,发现Gizmo的实现方式很有意思,虽然不难,但是给人一种很巧妙的感觉。所以有这篇文章。

关于Gizmo

从事游戏行业的同学只要用过商业引擎,那么Gizmo是一定使用过的。大多是用来显示表示物体的translate、rotate、scale等操作。本文给出rotation的实现方式。Gizmo与游戏Object之间的关系类似于观察者模式,其中设计模式的技巧用的很到位。关于引擎Object Tree的实现又是长篇大述,这里不再多说。

关于Rotation Gizmo的实现步骤

对于gizmo的实现,应用了一点3D数学的技巧。

  • 算法输入:screen级别的点 from与 点to。这两个点是点选Gizmo后拖动的起始点和终点。Gizmo利用这两个点来完成相应的计算。
  • 算法步骤:
    (1)将点from与to从Screen Space转移到World Space。这个过程是逆向的光栅化流程,不再赘述。得到两个线段l1, l2。
    (2)计算l1,l2到对应平面的交点p1, p2。如果选择了X轴向,那么我们的平面是Plane{point(0,0,0), dir(1,0,0)}。学过线性代数的同学可以发现这是平面的代数形式。
    (3)计算p1,p2两点到原点(0,0,0)的向量之间的theta角度。这个可以使用点乘之后除以两者长度得到。(一个3D数学中经典的求角度公式)
    (4)判定p1,p2是否与相对应轴向的面的dir(对,就是上面那个dir)的叉乘是否小于零。用这一点来判断你的旋转方向是正向还是反向。
    (5)最后乘以(180 / pi)从而完成角度转换。

此时,我们完成了从drag动作到角度的一系列转换。
下面给出代码实现,基于QT的实现方案,看起来通俗易懂:


void RotateGizmo::drag(QPoint from, QPoint to, int scnWidth, int scnHeight, QMatrix4x4 proj, QMatrix4x4 view) {
    if (m_host == 0) return;
    Line l1 = screenPosToWorldRay(QVector2D(from), QVector2D(scnWidth, scnHeight), proj, view);
    Line l2 = screenPosToWorldRay(QVector2D(to), QVector2D(scnWidth, scnHeight), proj, view);
    QMatrix4x4 invModelMat = globalSpaceMatrix().inverted();
    l1 = invModelMat * l1;
    l2 = invModelMat * l2;
    if (m_axis == X) {
        QVector3D p1 = getIntersectionOfLinePlane(l1, { QVector3D(0, 0, 0), QVector3D(1, 0, 0) });
        QVector3D p2 = getIntersectionOfLinePlane(l2, { QVector3D(0, 0, 0), QVector3D(1, 0, 0) });
        float theta = qAcos(qMin(qMax(QVector3D::dotProduct(p1, p2) / p1.length() / p2.length(), -1.0f), 1.0f));
        if (QVector3D::dotProduct(QVector3D(1, 0, 0), QVector3D::crossProduct(p1, p2)) < 0)
            theta = -theta;
        rotate(theta * QVector3D(180.0f / 3.1415926f, 0.0f, 0.0f));
    } else if (m_axis == Y) {
        QVector3D p1 = getIntersectionOfLinePlane(l1, { QVector3D(0, 0, 0), QVector3D(0, 1, 0) });
        QVector3D p2 = getIntersectionOfLinePlane(l2, { QVector3D(0, 0, 0), QVector3D(0, 1, 0) });
        float theta = qAcos(qMin(qMax(QVector3D::dotProduct(p1, p2) / p1.length() / p2.length(), -1.0f), 1.0f));
        if (QVector3D::dotProduct(QVector3D(0, 1, 0), QVector3D::crossProduct(p1, p2)) < 0)
            theta = -theta;
        rotate(theta * QVector3D(0.0f, 180.0f / 3.1415926f, 0.0f));
    } else if (m_axis == Z) {
        QVector3D p1 = getIntersectionOfLinePlane(l1, { QVector3D(0, 0, 0), QVector3D(0, 0, 1) });
        QVector3D p2 = getIntersectionOfLinePlane(l2, { QVector3D(0, 0, 0), QVector3D(0, 0, 1) });
        float theta = qAcos(qMin(qMax(QVector3D::dotProduct(p1, p2) / p1.length() / p2.length(), -1.0f), 1.0f));
        if (QVector3D::dotProduct(QVector3D(0, 0, 1), QVector3D::crossProduct(p1, p2)) < 0)
            theta = -theta;
        rotate(theta * QVector3D(0.0f, 0.0f, 180.0f / 3.1415926f));
    }
}

你可能感兴趣的:(迈向游戏引擎工程师)