OSG编程指南<二十二>:基于OSG+Qt制作三维可视化软件客户端

OSG编程指南<二十二>:基于OSG+Qt制作三维可视化软件客户端

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 pNode = osgDB::readNodeFile(sFileName.toStdString());

pNode->setName(sGuid);

m_pRootNode->addChild(pOutline);

}

//删除

QList 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 (&aa);

switch (ea.getEventType())

{

case osgGA::GUIEventAdapter::DOUBLECLICK:

{

osgUtil::LineSegmentIntersector::Intersections intersections;

osg::ref_ptr 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 pGroup = dynamic_cast(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 pGroup = dynamic_cast(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);

}

4、源码下载

源码下载地址

📌 相关推荐