OpenGL实现鼠标控制绕着指定轴旋转

发布于:2025-07-24 ⋅ 阅读:(15) ⋅ 点赞:(0)

鼠标控制沿着指定轴旋转

有这么一个开发场景,选定指定轴,操作鼠标控制模型绕着着指定轴旋转。

代码仅供参考,可根据实际应用场景进行调整。

方案一:根据两次鼠标坐标来得出旋转方向、角度

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;
  • 根据两个向量计算出旋转角度,也可以为固定值;
  • 根据向量前两个维度计算旋转方向。

缺点

  • 依赖中心点的选取,这里直接选取屏幕中心,如果鼠标画圈没有包含中心点的话,也会出现用户不友好的现象;

网站公告

今日签到

点亮在社区的每一天
去签到