OpenGL鼠标控制沿着指定轴旋转
文章目录
- 鼠标控制沿着指定轴旋转
- 方案一:根据两次鼠标坐标来得出旋转方向、角度
- 步骤
- 问题
- 方案二:结合arkball算法实现用户友好旋转(推荐)
- 步骤
- 缺点
鼠标控制沿着指定轴旋转
有这么一个开发场景,选定指定轴,操作鼠标控制模型绕着着指定轴旋转。
代码仅供参考,可根据实际应用场景进行调整。
方案一:根据两次鼠标坐标来得出旋转方向、角度
void CCabinet3DScene::rotateByAxis(int axis, QMouseEvent *event)
{QVector3D offset = getMouseMoveOffset(event);m_lastPos = event->pos();SPoint3D sRotate = m_pData->GetRotate();QVector3D delta = QVector3D(sRotate.x, sRotate.y, sRotate.z);float rotate = 0.0f;bool bMove = true;// 根据axis参数约束移动旋转switch(axis){case enX:{if (offset.x() >= 0){rotate = (delta.x() + 1 > 360) ? (delta.x() + 1 - 360) : (delta.x() + 1);delta.setX(rotate);}else{rotate = (delta.x() - 1 < -360) ? (delta.x() - 1 + 360) : (delta.x() - 1);delta.setX(rotate);}}break;case enY:{if (offset.y() >= 0){rotate = (delta.y() + 1 > 360) ? (delta.y() + 1 - 360) : (delta.y() + 1);delta.setY(rotate);}else{rotate = (delta.y() - 1 < -360) ? (delta.y() - 1 + 360) : (delta.y() - 1);delta.setY(rotate);}}break;case enZ:{if (offset.z() >= 0){rotate = (delta.z() + 1 > 360) ? (delta.z() + 1 - 360) : (delta.z() + 1);delta.setZ(rotate);}else{rotate = (delta.z() - 1 < -360) ? (delta.z() - 1 + 360) : (delta.z() - 1);delta.setZ(rotate);}}break;default :bMove = false;break;}if (!bMove)return;if (m_screens.contains(m_curID))m_screens[m_curID]->setRotate(delta, true);update();
}QVector3D CCabinet3DScene::getMouseMoveOffset(QMouseEvent *event)
{makeCurrent();GLint viewport[4];GLdouble modelview[16], projection[16];glGetIntegerv(GL_VIEWPORT, viewport);glGetDoublev(GL_MODELVIEW_MATRIX, modelview);glGetDoublev(GL_PROJECTION_MATRIX, projection);float lastZ = 0.0f;int lastWinX = m_lastPos.x();int lastWinY = viewport[3] - m_lastPos.y();glReadPixels(lastWinX, lastWinY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &lastZ);double lastWorldX, lastWorldY, lastWorldZ;gluUnProject(lastWinX, lastWinY, lastZ, modelview, projection, viewport, &lastWorldX, &lastWorldY, &lastWorldZ);float currZ = 0.0f;int currWinX = event->pos().x();int currWinY = viewport[3] - event->pos().y();glReadPixels(currWinX, currWinY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &currZ);double currWorldX, currWorldY, currWorldZ;gluUnProject(currWinX, currWinY, currZ, modelview, projection, viewport, &currWorldX, &currWorldY, &currWorldZ);QVector3D delta = QVector3D(currWorldX - lastWorldX, currWorldY - lastWorldY, currWorldZ - lastWorldZ);doneCurrent();return delta * devicePixelRatioF();
}
步骤
- 根据鼠标curPos和lastPos世界坐标的差值计算delta;
- 根据选定的旋转轴,取出delta对应的分量;
- 根据其值设置旋转方向,固定旋转为1。
问题
- 用户体验不友好:只根据一个目标分量判断方向,当用户操作鼠标在其他分量上变化大时,呈现的效果不好…
- 鼠标做画圈操作,会出现模型来回摆动的现象。
- 逆时针、顺时针操作呈现的效果不是相反的。
方案二:结合arkball算法实现用户友好旋转(推荐)
void CCabinet3DScene::rotateByAxis(int axis, QMouseEvent *event)
{QVector3D va = mapToArcball(m_lastPos);QVector3D vb = mapToArcball(event->pos());// 计算方向和角度float angle = acos(qBound(-1.0f, QVector3D::dotProduct(va, vb), 1.0f));float cross = va.x() * vb.y() - va.y() * vb.x();float sign = cross >= 0 ? -1.0f : 1.0f;float constrainedAngle = sign * angle * 180.0f / M_PI; ; // 转为角度m_lastPos = event->pos();SPoint3D sRotate = m_pData->GetRotate();QVector3D delta = QVector3D(sRotate.x, sRotate.y, sRotate.z);float rotate = 0.0f;bool bMove = true;// 根据axis参数约束移动旋转switch(axis){case enX:{rotate = delta.x() + constrainedAngle;if(rotate >= 360 || rotate <= -360)rotate = 0;delta.setX(rotate);}break;case enY:{rotate = delta.y() + constrainedAngle;if(rotate >= 360 || rotate <= -360)rotate = 0;delta.setY(rotate);}break;case enZ:{rotate = delta.z() + constrainedAngle;if(rotate >= 360 || rotate <= -360)rotate = 0;delta.setZ(rotate);}break;default :bMove = false;break;}if (!bMove)return;if (m_screens.contains(m_curID))m_screens[m_curID]->setRotate(delta, true);update();
}QVector3D CCabinet3DScene::mapToArcball(QPointF pos)
{float x = (2.0f * pos.x() - width()) / width();float y = (height() - 2.0f * pos.y()) / height(); // y反转float z2 = 1.0f - x * x - y * y;float z = z2 > 0.0f ? sqrt(z2) : 0.0f;return QVector3D(x, y, z).normalized();
}
步骤
- 计算鼠标位置映射到球体的向量va、vb;
- 根据两个向量计算出旋转角度,也可以为固定值;
- 根据向量前两个维度计算旋转方向。
缺点
- 依赖中心点的选取,这里直接选取屏幕中心,如果鼠标画圈没有包含中心点的话,也会出现用户不友好的现象;