1、概述
经过前面很多章节课程的学习,我们基本上对于OSG的基础知识已经有了大概的了解。有些人可能已经迫不及待想要开发自己的三维可视化软件了。本节我们就制作一个简版的三维可视化软件。效果包括如下:
1) 利用开源库Assimpsdk实现目前多种主流三维格式的导入导出;
2)OSG场景管理器,可以管理场景数据;
3)鼠标双击高亮模型,显示模型属性信息;
4)动态添加场景模型节点和删除节点;
5)控制场景模型的显示隐藏;
6)双击场景节点自动聚焦视点至模型并高亮显示;
2、效果图
3、关键技术点
3.1 Qt中嵌入OSG视图窗口
利用UI设计器拖入QOpenGLWidget 控件,然后右键提升为osgQOpenGLWidget 类,进行初始化即可;
connect(ui.openGLWidget, &osgQOpenGLWidget::initialized, this, &QAssimpWidget::slotInitOsgWindow);
void QAssimpWidget::slotInitOsgWindow()
{
m_pOsgViewer = ui.openGLWidget->getOsgViewer();
m_pRootNode = new osg::Group;
m_pRootNode->setName("root");
m_pRootNode->addChild(createCornerAxis());
m_pOsgViewer->setSceneData(m_pRootNode);
m_pOsgViewer->setCameraManipulator(new osgGA::TrackballManipulator());
m_pOsgViewer->addEventHandler(new CustomNodePick);
}
3.2 OSG视图更新
当OsgViewer视图中新增模型之后,我们需要重新计算场景的视点并进行更新,参照OSG源码的处理如下:
void QAssimpWidget::updataViewer()
{
// 计算新的场景外包
osg::BoundingSphere boundingSphere;
osg::ComputeBoundsVisitor cbVisitor;
m_pRootNode->accept(cbVisitor);
osg::BoundingBox& bb = cbVisitor.getBoundingBox();
……………………………省略…………
double radius = osg::maximum(double(boundingSphere.radius()), 1e-6);
// 更新相机位置,俯视角度观察模型
m_pOsgViewer->setCameraManipulator(NULL);
osgGA::CameraManipulator* pManipulator = new osgGA::TrackballManipulator();
m_pOsgViewer->setCameraManipulator(pManipulator);
}
3.3 OSG视图变形处理
当OsgViewer视图中新增模型之后,由于设计的视图宽高比例不一定为4:3,会出现模型变形的情况,也需要进行处理。
void QAssimpWidget::updataViewer()
{
//根据分辨率确定合适的投影来保证显示的图形不变形
double fovy, aspectRatio, zNear, zFar;
m_pOsgViewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspectRatio, zNear, zFar);
double newAspectRatio = double(ui.openGLWidget->width()) / double(ui.openGLWidget->height());
double aspectRatioChange = newAspectRatio / aspectRatio;
if (aspectRatioChange != 1.0)
{
//设置投影矩阵
m_pOsgViewer->getCamera()->getProjectionMatrix() *= osg::Matrix::scale(1.0 / aspectRatioChange, 1.0, 1.0);
}
}
3.4 OSG清空场景
当Osg视图进行清理时候,需要删除所有数据。
// 清空场景
while (true)
{
int nCount = m_pRootNode->getNumChildren();
if (nCount == 1)
{
break;
}
int nIndex = m_pRootNode->getNumChildren() - 1;
if (m_pRootNode->getChild(nIndex)->getName() == "Axis")
{
continue;
}
m_pRootNode->removeChild(nIndex);
}
3.5 OSG控制模型显示隐藏
当Osg视图进行场景节点勾选和取消时,可以控制模型数据的显示和隐藏。
for (unsigned int i = 0; i < m_pRootNode->getNumChildren(); i++)
{
if (m_pRootNode->getChild(i)->getName() == sGuid.toStdString())
{
int nNodeMask = m_pRootNode->getChild(i)->getNodeMask();
m_pRootNode->getChild(i)->setNodeMask((nNodeMask == 0) ? 1 : 0);
break;
}
}
3.6 OSG添加删除节点
Osg视图添加及删除节点。
//添加
{
osg::ref_ptr<osg::Node> pNode = osgDB::readNodeFile(sFileName.toStdString());
pNode->setName(sGuid);
m_pRootNode->addChild(pOutline);
}
//删除
QList<QTreeWidgetItem*> selectedItems = getAllSelectedItems(ui.treeWidget);
for (QTreeWidgetItem* pItem : selectedItems)
{
QString sGuid = pItem->data(0, Qt::UserRole + 1).toString();
for (unsigned int i = 0; i < m_pRootNode->getNumChildren(); i++)
{
if (m_pRootNode->getChild(i)->getName() == sGuid.toStdString())
{
m_pRootNode->removeChild(i);
break;
}
}
}
3.7 OSG双击模型高亮显示
Osg视图中双击模型可以选中模型高亮。重载osgGA::GUIEventHandler的DOUBLECLICK消息,利用射线求交方式得到模型,并把模型轮廓osgFX::Outline显示出来。
// 处理鼠标点击选中模型高亮
class CustomNodePick :public osgGA::GUIEventHandler
{
virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
{
osgViewer::Viewer* pViewer = dynamic_cast<osgViewer::Viewer*> (&aa);
switch (ea.getEventType())
{
case osgGA::GUIEventAdapter::DOUBLECLICK:
{
osgUtil::LineSegmentIntersector::Intersections intersections;
osg::ref_ptr<osg::Node> pNode = new osg::Node();
if (pViewer->computeIntersections(ea.getX(), ea.getY(), intersections))
{
//得到选择的节点
osgUtil::LineSegmentIntersector::Intersection intersection = *intersections.begin();
osg::NodePath& nodePath = intersection.nodePath;
pNode = nodePath[nodePath.size() - 2];
std::string sName = pNode->getName();
sName += "outline";
osg::ref_ptr<osg::Group> pGroup = dynamic_cast<osg::Group*>(pViewer->getSceneData());
for (unsigned int i = 0; i < pGroup->getNumChildren(); i++)
{
if (pGroup->getChild(i)->getName() == sName)
{
int nNodeMask = pGroup->getChild(i)->getNodeMask();
pGroup->getChild(i)->setNodeMask((nNodeMask == 0) ? 1 : 0);
break;
}
}
}
else
{
osg::ref_ptr<osg::Group> pGroup = dynamic_cast<osg::Group*>(pViewer->getSceneData());
for (unsigned int i = 0; i < pGroup->getNumChildren(); i++)
{
std::string sName = pGroup->getChild(i)->getName();
if (sName.find("outline") != std::string::npos)
{
pGroup->getChild(i)->setNodeMask(0);
}
}
}
}
default:
return false;
}
}
};
3.8 OSG模型定位
Osg视图中双击模型可以将模型聚焦到当前视图中来。
if (pNode)
{
// 更新相机位置
osg::BoundingSphere boundingSphere = pNode->getBound();
double radius = osg::maximum(double(boundingSphere.radius()), 1e-6);
double dist = 3.5f * radius;
m_pOsgViewer->setCameraManipulator(NULL);
osgGA::CameraManipulator* pManipulator = new osgGA::TrackballManipulator();
pManipulator->setHomePosition(boundingSphere.center() + osg::Vec3d(0, -dist, 0), boundingSphere.center(), osg::Vec3d(0.0f, 0.0f, 1.0f));
m_pOsgViewer->setCameraManipulator(pManipulator);
}