目录
1. 需求的提出
2. 具体实现
2.1. 禁止场景跟随鼠标转动
2.2. 矩形框前置绘制
3. 附加说明
3.1. 颜色设置说明
3.2.矩形框显示和隐藏的另一种实现
有时需要在屏幕通过按住键盘上的某个键如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(); // 告知底层,外层顶点数据更改了,否则不会用新的坐标绘制矩形
如果以为只把顶点数据改了就能重新绘制新的矩形是错误的,不调用上述代码中的某一种,新矩形不会绘制。