目录
有时需要在屏幕通过按住键盘上的某个键如Ctrl键且按住鼠标左键,拖出一个矩形,实现框选三维物体,如下效果:

现在的问题是:
对第1节中提到的第1个问题,默认情况下osgViewer::Viewer事件处理器在鼠标左键按下并拖动时,整个场景会随鼠标一起转动。为了不让转动,可以通过改写osgViewer::Viewer的 osgGA::GUIEventHandler事件处理器,重载如下方法:
virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, osg::Object* obj, osg::NodeVisitor*nv)
当按住键盘上的某个键如Ctrl键且按住鼠标左键,让该函数返回true,这样后续的流程就不会处理鼠标拖动事件,三维物体也就不会跟随鼠标旋转了。
矩形框要绘制在所有三维物体的前面而不能被三维物体遮挡,这就要用到三维中的HUD技术(Head Up Display)。所谓HUD节点,说白了就是无论三维场景中的内容怎么改变,它都能在屏幕上固定位置显示的节点。实现要点:
实现代码如下:
- #include
- #include
- #include
- #include
- class selectBoxEventHandler: public osgGA::GUIEventHandler
- {
- public:
- selectBoxEventHandler(osg::ref_ptr
spHudCamera) - {
- m_spHudCamera = spHudCamera;
- }
- private:
- virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, osg::Object* obj, osg::NodeVisitor*nv)
- {
- m_pViewer = (osgViewer::Viewer*)(&aa);
- if (m_pViewer == nullptr)
- {
- return false;
- }
-
- auto width = m_pViewer->getCamera()->getViewport()->width();
- auto height = m_pViewer->getCamera()->getViewport()->height();
-
- /* 设置HUD相机为正投影,这样绘制的矩形框和鼠标拖动的框选框大小就一样了
- 且要设置正投影的区域和视图窗体一样大小,因为鼠标可以在窗体任何位置进行框选
- */
- m_spHudCamera->setProjectionMatrix(osg::Matrix::ortho2D(0, width, 0, height));
-
- auto eventType = ea.getEventType();
- switch (eventType)
- {
- case osgGA::GUIEventAdapter::KEYDOWN:
- {
- if ((osgGA::GUIEventAdapter::KEY_Control_L == ea.getKey())
- || (osgGA::GUIEventAdapter::KEY_Control_R == ea.getKey())) // Ctrl键被按下
- {
- m_ctrlKeyPressed = true;
- }
- }
- break;
- case osgGA::GUIEventAdapter::KEYUP:
- {
- if ((osgGA::GUIEventAdapter::KEY_Control_L == ea.getKey())
- || (osgGA::GUIEventAdapter::KEY_Control_R == ea.getKey())) // Ctrl键被释放
- {
- m_ctrlKeyPressed = false;
- }
- }
- break;
- case osgGA::GUIEventAdapter::PUSH: // 鼠标左键按下
- {
- auto buttonMask = ea.getButtonMask();
- auto bIsMouseBtn = buttonMask & osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON;
- if (bIsMouseBtn)
- {
- m_fStartPosX = ea.getX();
- m_fStartPosY = ea.getY();
-
- m_bPush = true;
- }
- else if (buttonMask & osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON) // 鼠标右键按下,则删除选择框
- {
- if (m_spOldNode != nullptr)
- {
- m_spHudCamera->removeChild(m_spOldNode);
- }
- }
- }
- break;
- case osgGA::GUIEventAdapter::RELEASE: // 释放鼠标左键
- {
- m_bPush = false;
- }
- break;
- case osgGA::GUIEventAdapter::DRAG: // 拖动鼠标
- {
- auto buttonMask = ea.getButtonMask();
- auto bIsMouseBtn = buttonMask & osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON;
- if (bIsMouseBtn && m_ctrlKeyPressed && m_bPush)
- {
- m_fEndPosX = ea.getX();
- m_fEndPosY = ea.getY();
- auto pSelectBox = createSelectBox(m_fStartPosX, m_fStartPosY, m_fEndPosX, m_fEndPosY);
- if (m_spOldNode != nullptr)
- {
- m_spHudCamera->removeChild(m_spOldNode);
- }
-
- m_spHudCamera->addChild(pSelectBox);
- m_spOldNode = pSelectBox;
- return true;
- }
- }
- } // end swith
-
- return false;
- }
-
- osg::Geode* createSelectBox(float fStartPosX, float fStartPosY, float fEndPosX, float fEndPosY)
- {
- osg::Geode* pGeode = new osg::Geode();
- auto pQuardGeomerty = new osg::Geometry();
- pGeode->addChild(pQuardGeomerty);
-
- osg::Vec3Array* pVertArray = new osg::Vec3Array;
- pVertArray->push_back(osg::Vec3(fStartPosX, fStartPosY, 0.0));
- pVertArray->push_back(osg::Vec3(fStartPosX, fEndPosY, 0.0));
- pVertArray->push_back(osg::Vec3(fEndPosX, fEndPosY, 0.0));
- pVertArray->push_back(osg::Vec3(fEndPosX, fStartPosY, 0.0));
- pQuardGeomerty->setVertexArray(pVertArray);
-
- osg::Vec4Array* pColorArray = new osg::Vec4Array;
- pColorArray->push_back(osg::Vec4(1.0, 1.0, 1.0, 0.4));
- /* pColorArray->push_back(osg::Vec4(1.0, 1.0, 1.0, 0.40));
- pColorArray->push_back(osg::Vec4(1.0, 1.0, 1.0, 0.40));
- pColorArray->push_back(osg::Vec4(1.0, 1.0, 1.0, 0.40));*/
- pQuardGeomerty->setColorArray(pColorArray);
- //pQuardGeomerty->setColorBinding(osg::Geometry::AttributeBinding::BIND_PER_VERTEX);
- pQuardGeomerty->setColorBinding(osg::Geometry::AttributeBinding::BIND_PER_PRIMITIVE_SET);
-
- pQuardGeomerty->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));
- pGeode->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); // 关闭光照
- pGeode->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); // 开启透明,否则就不能透过选择框看到后面的牛
- pGeode->addDrawable(pQuardGeomerty);
-
- return pGeode;
- }
-
-
- private:
-
- /* 鼠标按下的起始坐标点 */
- float m_fStartPosX{0.0};
- float m_fStartPosY{ 0.0 };
- float m_fEndPosX{ 0.0 };
- float m_fEndPosY{ 0.0 };
-
- bool m_ctrlKeyPressed{false}; // Ctrl键被按下
- bool m_bPush{false}; // 鼠标左键是否被按下
- osgViewer::Viewer* m_pViewer{nullptr};
- osg::ref_ptr
m_spHudCamera; // 用于HUD的相机 - osg::ref_ptr
m_spOldNode; // 上次鼠标框选绘制出的矩形框 -
- };
- int main(int argc, char *argv[])
- {
- osgViewer::Viewer viewer;
- auto cowNode = osgDB::readNodeFile(R"(E:\osg\OpenSceneGraph-Data\cow.osg)");
- if (nullptr == cowNode)
- {
- OSG_WARN << "node is null!";
- return 1;
- }
-
- auto spRoot = new osg::Group();
-
- osg::ref_ptr
spHudCamera = new osg::Camera; - spHudCamera->setClearMask(GL_DEPTH_BUFFER_BIT); // 关闭深度缓冲
-
- // 设置渲染顺序为后渲染,即始终在其它绘制物体的上面,防止被其它绘制的物体遮挡
- spHudCamera->setRenderOrder(osg::Camera::RenderOrder::POST_RENDER);
- spHudCamera->setAllowEventFocus(false); // 不接受任何焦点事件,即不响应键盘、鼠标事件
- spHudCamera->setReferenceFrame(osg::Transform::ReferenceFrame::ABSOLUTE_RF); // 设置参考帧为绝对帧
- spHudCamera->setViewMatrix(osg::Matrix::identity()); // 设置相机视图矩阵为单位矩阵,这样就矩形框选框就不受相机旋转等变换影响
-
- spRoot->addChild(cowNode);
- spRoot->addChild(spHudCamera);
-
- viewer.setSceneData(spRoot);
- viewer.addEventHandler(new selectBoxEventHandler(spHudCamera));
- return viewer.run();
- }
2.2节代码对颜色的设置,也可以按如下代码一样达到同样的效果:
- osg::Vec4Array* pColorArray = new osg::Vec4Array;
- pColorArray->push_back(osg::Vec4(1.0, 1.0, 1.0, 0.4));
- pColorArray->push_back(osg::Vec4(1.0, 1.0, 1.0, 0.40));
- pColorArray->push_back(osg::Vec4(1.0, 1.0, 1.0, 0.40));
- pColorArray->push_back(osg::Vec4(1.0, 1.0, 1.0, 0.40));
- pQuardGeomerty->setColorArray(pColorArray);
- pQuardGeomerty->setColorBinding(osg::Geometry::AttributeBinding::BIND_PER_VERTEX);
也就是说设置一个顶点的颜色且颜色绑定方式为BIND_PER_PRIMITIVE_SET和分别设置4个顶点颜色,颜色绑定方式为BIND_PER_VERTEX效果相同。关于BIND_PER_PRIMITIVE_SET和BIND_PER_VERTEX的具体含义和不同点,请参考:osg图元绑定方式总结博文。
上面矩形框的显示和隐藏是通过removeChild和addChild函数来实现的,即将新的矩形框节点加入到相机作为其子节点之前,删除上次创建的矩形框节点。也可以通过osg::Node的setNodeMask函数来实现,如下为更改后的代码:
- #include
- #include
- #include
- #include
-
-
- #define HIDE_SELECT_BOX 0X0
- #define SHOW_SELECT_BOX ~HIDE_SELECT_BOX
-
- class selectBoxEventHandler : public osgGA::GUIEventHandler
- {
- public:
- selectBoxEventHandler(osg::ref_ptr
spHudCamera) - {
- m_spHudCamera = spHudCamera;
- }
- private:
- virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa, osg::Object* obj, osg::NodeVisitor* nv)
- {
- m_pViewer = (osgViewer::Viewer*)(&aa);
- if (m_pViewer == nullptr)
- {
- return false;
- }
-
- auto width = m_pViewer->getCamera()->getViewport()->width();
- auto height = m_pViewer->getCamera()->getViewport()->height();
-
- /* 设置HuD相机为正投影,这样绘制的矩形框和鼠标拖动的框选框大小就一样了
- 且要设置正投影的区域和视图窗体一样大小,因为鼠标可以在窗体任何位置进行框选
- */
- m_spHudCamera->setProjectionMatrix(osg::Matrix::ortho2D(0, width, 0, height));
-
- auto eventType = ea.getEventType();
- switch (eventType)
- {
- case osgGA::GUIEventAdapter::KEYDOWN:
- {
- if ((osgGA::GUIEventAdapter::KEY_Control_L == ea.getKey())
- || (osgGA::GUIEventAdapter::KEY_Control_R == ea.getKey())) // Ctrl键被按下
- {
- m_ctrlKeyPressed = true;
- }
- }
- break;
- case osgGA::GUIEventAdapter::KEYUP:
- {
- if ((osgGA::GUIEventAdapter::KEY_Control_L == ea.getKey())
- || (osgGA::GUIEventAdapter::KEY_Control_R == ea.getKey())) // Ctrl键被按下
- {
- m_ctrlKeyPressed = false;
- }
- }
- break;
- case osgGA::GUIEventAdapter::PUSH: // 鼠标左键按下
- {
- auto buttonMask = ea.getButtonMask();
- auto bIsMouseBtn = buttonMask & osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON;
- if (bIsMouseBtn)
- {
- m_fStartPosX = ea.getX();
- m_fStartPosY = ea.getY();
-
- m_bPush = true;
- }
- else if (buttonMask & osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON) // 鼠标右键按下,则删除选择框
- {
- if (m_spRectGeometry != nullptr)
- {
- m_spRectGeometry->setNodeMask(HIDE_SELECT_BOX);
- }
- }
- }
- break;
- case osgGA::GUIEventAdapter::RELEASE: // 释放鼠标左键
- {
- m_bPush = false;
- }
- break;
- case osgGA::GUIEventAdapter::DRAG: // 拖动鼠标
- {
- auto buttonMask = ea.getButtonMask();
- auto bIsMouseBtn = buttonMask & osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON;
- if (bIsMouseBtn && m_ctrlKeyPressed && m_bPush)
- {
- m_fEndPosX = ea.getX();
- m_fEndPosY = ea.getY();
- if (nullptr != m_spRectGeometry)
- {
- auto pVertArray = (osg::Vec3Array*)m_spRectGeometry->getVertexArray();
- (*pVertArray)[0].set(m_fStartPosX, m_fStartPosY, 0.0);
- (*pVertArray)[1].set(m_fStartPosX, m_fEndPosY, 0.0);
- (*pVertArray)[2].set(m_fEndPosX, m_fEndPosY, 0.0);
- (*pVertArray)[3].set(m_fEndPosX, m_fStartPosY, 0.0);
- // m_spRectGeometry->setVertexArray(pVertArray);
- m_spRectGeometry->dirtyDisplayList(); // 告知底层,外层顶点数据更改了,否则不会用新的坐标绘制矩形
- m_spRectGeometry->setNodeMask(SHOW_SELECT_BOX);
- }
- else
- {
- auto spSelectBox = createSelectBox(m_fStartPosX, m_fStartPosY, m_fEndPosX, m_fEndPosY);
- m_spRectGeometry = spSelectBox->asGeode()->getChild(0)->asGeometry();
- m_spHudCamera->addChild(spSelectBox);
-
- }
-
- return true;
- }
- }
- } // end swith
-
- return false;
- }
-
- osg::Geode* createSelectBox(float fStartPosX, float fStartPosY, float fEndPosX, float fEndPosY)
- {
- osg::Geode* pGeode = new osg::Geode();
- auto pQuardGeomerty = new osg::Geometry();
- pGeode->addChild(pQuardGeomerty);
-
- osg::Vec3Array* pVertArray = new osg::Vec3Array;
- pVertArray->push_back(osg::Vec3(fStartPosX, fStartPosY, 0.0));
- pVertArray->push_back(osg::Vec3(fStartPosX, fEndPosY, 0.0));
- pVertArray->push_back(osg::Vec3(fEndPosX, fEndPosY, 0.0));
- pVertArray->push_back(osg::Vec3(fEndPosX, fStartPosY, 0.0));
- pQuardGeomerty->setVertexArray(pVertArray);
-
- osg::Vec4Array* pColorArray = new osg::Vec4Array;
- pColorArray->push_back(osg::Vec4(1.0, 1.0, 1.0, 0.4));
- /* pColorArray->push_back(osg::Vec4(1.0, 1.0, 1.0, 0.40));
- pColorArray->push_back(osg::Vec4(1.0, 1.0, 1.0, 0.40));
- pColorArray->push_back(osg::Vec4(1.0, 1.0, 1.0, 0.40));*/
- pQuardGeomerty->setColorArray(pColorArray);
- //pQuardGeomerty->setColorBinding(osg::Geometry::AttributeBinding::BIND_PER_VERTEX);
- pQuardGeomerty->setColorBinding(osg::Geometry::AttributeBinding::BIND_PER_PRIMITIVE_SET);
-
- pQuardGeomerty->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));
- pGeode->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); // 关闭光照
- pGeode->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON); // 开启透明,否则就不能透过选择框看到后面的牛
- pGeode->addDrawable(pQuardGeomerty);
-
- return pGeode;
- }
-
-
- private:
-
- /* 鼠标按下的起始坐标点 */
- float m_fStartPosX{ 0.0 };
- float m_fStartPosY{ 0.0 };
- float m_fEndPosX{ 0.0 };
- float m_fEndPosY{ 0.0 };
-
- bool m_ctrlKeyPressed{ false }; // Ctrl键被按下
- bool m_bPush{ false }; // 鼠标左键是否被按下
- osgViewer::Viewer* m_pViewer{ nullptr };
- osg::ref_ptr
m_spHudCamera; // 用于HUD的相机 - osg::ref_ptr
m_spRectGeometry; // 矩形框 -
- };
-
- int main(int argc, char *argv[])
- {
- osgViewer::Viewer viewer;
-
- auto cowNode = osgDB::readNodeFile(R"(E:\osg\OpenSceneGraph-Data\cow.osg)");
- if (nullptr == cowNode)
- {
- OSG_WARN << "node is null!";
- return 1;
- }
-
- auto spRoot = new osg::Group();
-
- osg::ref_ptr
spHudCamera = new osg::Camera; - spHudCamera->setClearMask(GL_DEPTH_BUFFER_BIT); // 开启深度缓冲
-
- // 设置渲染顺序为后渲染,即始终在其它绘制物体的上面,防止被其它绘制的物体遮挡
- spHudCamera->setRenderOrder(osg::Camera::RenderOrder::POST_RENDER);
- spHudCamera->setAllowEventFocus(false); // 不接受任何焦点事件,即不响应键盘、鼠标事件
- spHudCamera->setReferenceFrame(osg::Transform::ReferenceFrame::ABSOLUTE_RF); // 设置参考帧为绝对帧
- spHudCamera->setViewMatrix(osg::Matrix::identity()); // 设置相机视图矩阵为单位矩阵,这样就矩形框选框就不受相机旋转等变换影响
-
- spRoot->addChild(cowNode);
- spRoot->addChild(spHudCamera);
-
- viewer.setSceneData(spRoot);
- viewer.addEventHandler(new selectBoxEventHandler(spHudCamera));
- return viewer.run();
- }
说明:
- // 告知底层,外层顶点数据更改了,否则不会用新的坐标绘制矩形
- m_spRectGeometry->setVertexArray(pVertArray);
或调用:
m_spRectGeometry->dirtyDisplayList(); // 告知底层,外层顶点数据更改了,否则不会用新的坐标绘制矩形
在C++中,只要把指针指向的内容改了,也就是立马改了,但在上述代码中如果以为只把顶点数据改了,新矩形就会呈现出来是错误的,不调用上述代码中的某一种,新矩形不会绘制,因为只改了顶点数据,但osg还没执行重绘。