OpenGL ArcBall用于旋转网格

时间:2019-02-02 23:03:52

标签: c++ opengl arcball

我正在使用旧版OpenGL绘制网格。我现在正在尝试实现一个arcball类,以使用鼠标旋转对象。但是,当我移动鼠标时,对象要么不旋转,要么旋转角度太大。

这是单击鼠标时调用的方法:

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

应用旋转

1 个答案:

答案 0 :(得分:0)

我建议将鼠标位置存储在您最初在视图中单击的位置。计算窗口坐标中的鼠标移动量。运动的距离必须映射到一个角度。旋转轴垂直于鼠标移动方向(垂直)。结果是旋转类似于this 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),可以通过(-y,x)逆时针旋转90度。

方向矢量的长度表示旋转角度。鼠标在整个屏幕上移动会产生一个长度为 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; 

方法startRotationupdateRotation的最终代码清单:

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;
}