写这篇小文章,是发现网上对于C++实现的Gizmo实现的描述太少了,大多都是在讲Unity Gizmo怎么使用。而我最近在研究开发自己的图形引擎,Gizmo的实现成为了我的一个障碍。研究一下,发现Gizmo的实现方式很有意思,虽然不难,但是给人一种很巧妙的感觉。所以有这篇文章。
从事游戏行业的同学只要用过商业引擎,那么Gizmo是一定使用过的。大多是用来显示表示物体的translate、rotate、scale等操作。本文给出rotation的实现方式。Gizmo与游戏Object之间的关系类似于观察者模式,其中设计模式的技巧用的很到位。关于引擎Object Tree的实现又是长篇大述,这里不再多说。
对于gizmo的实现,应用了一点3D数学的技巧。
此时,我们完成了从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));
}
}