c++ - 用于旋转网格的 OpenGL ArcBall
问题描述
我正在使用旧版 OpenGL 来绘制网格。我现在正在尝试实现一个轨迹球类来用鼠标旋转对象。但是,当我移动鼠标时,对象要么不旋转,要么旋转角度太大。
这是单击鼠标时调用的方法:
void ArcBall::startRotation(int xPos, int yPos) {
int x = xPos - context->getWidth() / 2;
int y = context->getHeight() / 2 - yPos;
startVector = ArcBall::mapCoordinates(x, y).normalized();
endVector = startVector;
rotating = true;
}
此方法旨在简单地将鼠标坐标映射到屏幕中心,并将它们映射到边界球体,从而产生一个起始向量
这是鼠标移动时调用的方法:
void ArcBall::updateRotation(int xPos, int yPos) {
int x = xPos - context->getWidth() / 2;
int y = context->getHeight() / 2 - yPos;
endVector = mapCoordinates(x, y).normalized();
rotationAxis = QVector3D::crossProduct(endVector, startVector).normalized();
angle = (float)qRadiansToDegrees(acos(QVector3D::dotProduct(startVector, endVector)));
rotation.rotate(angle, rotationAxis.x(), rotationAxis.y(), rotationAxis.z());
startVector = endVector;
}
此方法再次将鼠标坐标映射到屏幕中间,然后计算新向量并根据这两个向量计算旋转轴和角度。
然后我用
glMultMatrixf(ArcBall::rotation.data());
应用旋转
解决方案
我建议将鼠标位置存储在您最初在视图中单击的位置。计算鼠标在窗口坐标中的移动量。运动的距离必须映射到一个角度。旋转轴垂直(法线)于鼠标移动的方向。结果是类似于此WebGL 演示的对象的旋转。
将当前鼠标位置存储在startRotation
. 注意存储未归一化向量的位置鼠标位置的坐标:
// xy normalized device coordinates:
float ndcX = 2.0f * xPos / context->getWidth() - 1.0f;
float ndcY = 1.0 - 2.0f * yPos / context->getHeight();
startVector = QVector3D(ndcX, ndcY, 0.0);
获取当前位置updateRotation
:
// xy normalized device coordinates:
float ndcX = 2.0f * xPos / context->getWidth() - 1.0f;
float ndcY = 1.0 - 2.0f * yPos / context->getHeight();
endVector = QVector3D(ndcX, ndcY, 0.0);
计算从开始位置到结束位置的向量:
QVector3D direction = endVector - startVector;
旋转轴垂直于运动方向:
rotationAxis = QVector3D(-direction.y(), direction.x(), 0.0).normalized();
请注意,即使 的类型direction
是QVector3D
,它仍然是一个二维向量。它是视口 XY 平面中的一个向量,表示鼠标在视口上的移动。z 坐标为 0。二维向量(x, y)可以逆时针旋转 90 度(-y, x)。
方向矢量的长度表示旋转角度。鼠标在整个屏幕上移动会产生一个长度为2.0的向量。因此,如果在整个屏幕上拖动会导致完全旋转,则向量的长度必须乘以PI。如果应该执行半旋转,那么通过PI/2:
angle = (float)qRadiansToDegrees(direction.length() * 3.141593);
最后,必须将新旋转应用于现有旋转而不是模型:
QMatrix4x4 addRotation;
addRotation.rotate(angle, rotationAxis.x(), rotationAxis.y(), rotationAxis.z());
rotation = addRotation * rotation;
方法的最终代码清单startRotation
和updateRotation
:
void ArcBall::startRotation(int xPos, int yPos) {
// xy normalized device coordinates:
float ndcX = 2.0f * xPos / context->getWidth() - 1.0f;
float ndcY = 1.0 - 2.0f * yPos / context->getHeight();
startVector = QVector3D(ndcX, ndcY, 0.0);
endVector = startVector;
rotating = true;
}
void ArcBall::updateRotation(int xPos, int yPos) {
// xy normalized device coordinates:
float ndcX = 2.0f * xPos / context->getWidth() - 1.0f;
float ndcY = 1.0 - 2.0f * yPos / context->getHeight();
endVector = QVector3D(ndcX, ndcY, 0.0);
QVector3D direction = endVector - startVector;
rotationAxis = QVector3D(-direction.y(), direction.x(), 0.0).normalized();
angle = (float)qRadiansToDegrees(direction.length() * 3.141593);
QMatrix4x4 addRotation;
addRotation.rotate(angle, rotationAxis.x(), rotationAxis.y(), rotationAxis.z());
rotation = addRotation * rotation;
startVector = endVector;
}
如果您想要围绕对象的向上轴旋转并且沿着视图空间 x 轴倾斜对象,那么计算是不同的。首先应用绕 y 轴(向上向量)的旋转矩阵,然后应用当前视图矩阵,最后应用 x 轴上的旋转:
view-matrix = rotate-X * view-matrix * rotate-Y
函数更新轮换必须如下所示:
void ArcBall::updateRotation(int xPos, int yPos) {
// xy normalized device coordinates:
float ndcX = 2.0f * xPos / context->getWidth() - 1.0f;
float ndcY = 1.0 - 2.0f * yPos / context->getHeight();
endVector = QVector3D(ndcX, ndcY, 0.0);
QVector3D direction = endVector - startVector;
float angleY = (float)qRadiansToDegrees(-direction.x() * 3.141593);
float angleX = (float)qRadiansToDegrees(-direction.y() * 3.141593);
QMatrix4x4 rotationX;
rotationX.rotate(angleX, 1.0f 0.0f, 0.0f);
QMatrix4x4 rotationUp;
rotationX.rotate(angleY, 0.0f 1.0f, 0.0f);
rotation = rotationX * rotation * rotationUp;
startVector = endVector;
}
推荐阅读
- javascript - VueJS 事件 $on 的问题 - 多次运行
- mongodb - Mongoose 不返回数据进行第二次收集
- jquery - 当滚动条不存在时,jquery width() 不正确
- java - Android Studio 无法下载 commons-compress-1.12.jar,我可以在浏览器上下载 commons-compress-1.12.jar 并粘贴到 gradle 上的位置吗?
- python - 查找三次函数的局部最小值/最大值
- python - 如何反转列表中给定的布尔值?
- neo4j - 如何在 neo4j 中删除数据集中的 NULL 值?
- python - Pandas - 依靠特定列并获得前 N 个列表
- android - android p APK运行时找不到AppCompatViewInflater
- plot - 如何在 Julia 上绘制矢量场?