前面说了如何加载外部的3D模型,在加入之后我们要弄清楚Qt在显示3D场景时空间坐标系是如何构造的,这里介绍一个我的经验,可以在加载外部3D模型之前先用Qt自带的Qt3DExtras::QCylinderMesh,也就是圆柱体的对象来画出空间坐标系中X,Y,Z三个坐标轴。
画坐标轴的代码如下:
/****************************坐标系*******************************/
//用圆柱体画坐标系
Qt3DExtras::QCylinderMesh *cylinder_x = new Qt3DExtras::QCylinderMesh();
cylinder_x->setRadius(0.1);//半径,不能太胖了,细细的一条线就可以了
cylinder_x->setLength(10);//长度
Qt3DExtras::QCylinderMesh *cylinder_y = new Qt3DExtras::QCylinderMesh();
cylinder_y->setRadius(0.1);//半径
cylinder_y->setLength(10);//长度
Qt3DExtras::QCylinderMesh *cylinder_z = new Qt3DExtras::QCylinderMesh();
cylinder_z->setRadius(0.1);//半径
cylinder_z->setLength(10);//长度
// CylinderMesh Transform
//这里的Transform非常重要,可以看到它有设置选择角度,设置位置两个函数,后面要让模型动起来
//就是要靠这些函数
Qt3DCore::QTransform *cylinderTransform_x = new Qt3DCore::QTransform();
cylinderTransform_x->setScale(1.0f);//上面的长度和半径设置好了,这里就不用缩放了
cylinderTransform_x->setRotationZ(90.0f);//想想看这里为啥要绕Z轴转90°
cylinderTransform_x->setTranslation(QVector3D(5,0,0));//要理解QVector3D这个向量的意思
//这里setRotation和setTranslation要好好看看Qt的帮助文档,后面让模型动起来就是靠它俩!!!!!!
Qt3DCore::QTransform *cylinderTransform_y = new Qt3DCore::QTransform();
cylinderTransform_y->setScale(1.0f);
cylinderTransform_y->setTranslation(QVector3D(0,5,0));
Qt3DCore::QTransform *cylinderTransform_z = new Qt3DCore::QTransform();
cylinderTransform_z->setScale(1.0f);
cylinderTransform_z->setRotationX(90.0f);
cylinderTransform_z->setTranslation(QVector3D(0,0,5));
Qt3DExtras::QPhongMaterial *cylinderMaterial = new Qt3DExtras::QPhongMaterial();
cylinderMaterial->setDiffuse(QColor(QRgb(0x00ee00)));
// Cylinder
//可以看到每个圆柱体都调用了三次addComponent,添加进来的分别是模型本身,模型的材质,以及
//那个Transform,这些都是固定的套路
m_cylinderEntity[0] = new Qt3DCore::QEntity(rootEntity);
m_cylinderEntity[0]->addComponent(cylinder_x);
m_cylinderEntity[0]->addComponent(cylinderMaterial);
m_cylinderEntity[0]->addComponent(cylinderTransform_x);
m_cylinderEntity[1] = new Qt3DCore::QEntity(rootEntity);
m_cylinderEntity[1]->addComponent(cylinder_y);
m_cylinderEntity[1]->addComponent(cylinderMaterial);
m_cylinderEntity[1]->addComponent(cylinderTransform_y);
m_cylinderEntity[2] = new Qt3DCore::QEntity(rootEntity);
m_cylinderEntity[2]->addComponent(cylinder_z);
m_cylinderEntity[2]->addComponent(cylinderMaterial);
m_cylinderEntity[2]->addComponent(cylinderTransform_z);
/**************************坐标系************************************/
有了这个三维坐标系把外部3D模型导入进来时就可以做到心中有数了。
那么,怎么让模型动起来呢?这里要借用一下机器人中常用的多轴联动的概念,我们经常可以看到某某工业机器人是5轴联动的,或者是6轴联动的,其实就是这个概念。我们把导入的模型按照所属运动轴来划分,比如你的整个模型是3轴联动的,那么就要把整个模型拆成3个部分进行导入,然后根据这3个部分的实际位置,在Qt3D的场景中先把这3个部分拼起来!所谓拼起来就要为每个部分的Qt3DCore::QTransform根据实际他们的相对位置设置一个合适的QVector3D,然后调用setTranslation即可。
拼接完成之后让它们动起来其实就是转换成一个数学问题了:让运动轴旋转和变换时,与它相连的其它轴如何运动?由于我的项目中涉及到的运动轴很多,废了我很多脑细胞也没能让模型按照我的要求动起来,经常一动模型的各个部分就跑飞了,后来请教了算法相关的同事,终于把这个问题给解决了!我把模型的初始坐标等参数给他,他给了我列了一大堆的公式,根据这些公式最终可以计算出每个轴相对于运动轴的位置和角度的变换,然后调用setRotation和setTranslation,这样每个轴终于按照我的要求动起来了!完结!撒花!
最后,说几个需要注意的地方:
1,void setRotation(const QQuaternion &rotation)这个函数中,要注意一下QQuaternion 这个类。我使用的是其中的QQuaternion::fromAxisAndAngle()也就是所谓的四元数的方法,这里我试过不用四元数直接用角度值,发现旋转有问题,这里可能涉及到3D方面的概念,反正我也弄不太清楚。
2,用来显示3D场景的那个widget,当鼠标放上去时,可以响应鼠标的拖动以及滑轮等事件,虽然这些事件我并没有去实现它。猜测应该是Qt3D内部实现的。当这样就有一个问题,就是我无法禁止这些事件,我试了很多种办法都没有用,比如:改写鼠标/滑轮事件啊,加事件过滤器啊,给这个widget加一个透明的遮罩窗口啊等等,反正都是不行!最后没办法,我在界面上加了一个复位按钮,如果不小心把模型拖离了显示的view之外,点击复位按钮可以把模型的位置重置回来。________________________________________________________________________________________________________________________________
更新:对照上一篇博客中说的Qt官方示例,只要把如下的代码屏蔽就可以禁止掉鼠标事件了。这样上面的问题就解决了。
// 设置相机控制,什么第一视场什么的,不是很懂,待补充。
Qt3DExtras::QFirstPersonCameraController *camController = new Qt3DExtras::QFirstPersonCameraController(rootEntity);
camController->setCamera(cameraEntity);
_________________________________________________________________________________________________________________________________
再次更新,Qt还可以为场景中的每个实体添加一个鼠标拾取功能,只需要用实体作为参数构造一个对象拾取器类即可,并且可以连接多种不同的信号,代码如下:
Qt3DRender::QObjectPicker *picker = new Qt3DRender::QObjectPicker(具体的实体);
picker->setHoverEnabled(true);
picker->setEnabled(true);
connect(picker, &Qt3DRender::QObjectPicker::clicked, .......................);
connect(picker, &Qt3DRender::QObjectPicker::pressed, .......................);
connect(picker, &Qt3DRender::QObjectPicker::released, .......................);
connect(picker, &Qt3DRender::QObjectPicker::entered, .......................);
connect(picker, &Qt3DRender::QObjectPicker::exited, .......................);
最后调用实体的addComponent()方法,把picker也添加进实体即可。