第37.2节 框选-框选场景中的物体

本节内容

结合上一节,我们把框选这个功能给完善了,如下,白色的是我点击左CTRL,用鼠标左键在场景中拉的框,拉框的教程在第37.1节 框选-绘制框选框,拉完框后能够将场景中选择的物体置红。
在这里插入图片描述
本节代码在如下网盘中,根据章节编号有对应附件,请使用浏览器打开,平时遇到问题或加群也可以加我微信:13324598743:
【击此打开网盘资源链接】

实现要点

点选

在事件响应中,我们的点选是使用computeIntersections函数,它的重载很多,此处我们使用如下重载:
computeIntersections(i, j, intersections, NODE_PICK))
其中i, j是窗口坐标,intersections是pick的结果,NODE_PICK是一个结点掩码,我们只对和这个掩码求&=true的。比如一个结点的掩码是0XF0,而NODE_PICK=0X0F,则不会对这个结点求交。

性能

性能在框选操作中是非常关键的,在这里我们有以下几个措施。

一个是框选的时候是拉一个框,拉的过程中已经参加过点选的坐标我们就不再参加点选的,我们定义了一个变量std::vectorosg::Vec2i _pickArea;把已经点选的坐标记录在内。拉的过程中只点选新的坐标。

另一个是结点已经点选过且被选中的,结点不再需要参加点选,我们通过置掩码,:

#define NODE_NPICK ~0x0F
#define NODE_PICK 0x0F

其中NODE_PICK是computeIntersections传入的,当结点的掩码是NODE_NPICK 的时候,则不会参加点选,因为NODE_NPICK &NODE_PICK =0

再一个我们每隔5个像素点选一次。而不是很个像素都点选,这会对精度造成一定影响。

关于性能的提升本节提供的是一些常见思路,大家也可以优化求交的方法来对场景做选择等等。

绘制球

本文通过继承public osg::Geode 来绘制一个球。设置了两种状态,选择与没有被选择,用颜色来区分他们。

###以下为全部代码

#include <osgViewer/Viewer>
#include <osgDB/ReadFile>
#include <osg/Camera>
#include <osgGA/GUIEventHandler>
#include <osg/Geode>
#include <osg/ShapeDrawable>
#include <osg/Geometry>
#include <algorithm>


//掩码为NODE_NPICK则不接受PICK,为NODE_PICK为接受PICK
#define NODE_NPICK ~0x0F
#define NODE_PICK 0x0F

//绘制一些球,用于被选择
class MySphere : public osg::Geode
{
public:
    MySphere(osg::Vec3 center, float radius)
    {
        _bSelect = false;
        _sd = new osg::ShapeDrawable(new osg::Sphere(center, radius));
        _sd->setColor(osg::Vec4(0.5, 0.5, 0.5, 1.0));
        addDrawable(_sd);
        setNodeMask(NODE_PICK);
    }

    //设置球是否被选择
    void setSelect(bool bSelect)
    {
        if (_bSelect == bSelect)
        {
            return;
        }

        _bSelect = bSelect;
        if (_bSelect)
        {
            _sd->setColor(osg::Vec4(1.0, 0.2, 0.2, 1.0));
            setNodeMask(NODE_NPICK);
        }
        else
        {
            _sd->setColor(osg::Vec4(0.5, 0.5, 0.5, 1.0));
            setNodeMask(NODE_PICK);
        }

        //重绘
        _sd->dirtyDisplayList();
    }

    osg::ShapeDrawable* _sd;
    bool _bSelect;
};

//用于选择的场景
osg::Node* g_selectNode = nullptr;

//绘制很多球
osg::Node* BuildScene()
{
    osg::Group* root = new osg::Group;

    //这些小球可以被选择
    g_selectNode = root;
    //绘制100个球
    for (int i = 0; i < 100; i++)
    {
        osg::Vec3 center(::rand() % 100, ::rand() % 100, ::rand() % 100);
        float r = ::rand() % 10+1.0;
        root->addChild(new MySphere(center, r));
    }

    return root;
}

//清空点选状态
void ClearSelect()
{
    for (int i = 0; i < 100; i++)
    {
        ((MySphere*)g_selectNode->asGroup()->getChild(i))->setSelect(false);
    }
}

//结点的掩码,显示与隐藏
#define NODE_SHOW ~0x0
#define NODE_HIDE 0x0

//选择框做为一个全局变量,使用起来方便
osg::Geometry* g_geomSelectBox = new osg::Geometry;

//
osg::Camera* createHUD(osg::Viewport* vp)
{
    osg::Camera* camera = new osg::Camera;

    //设置投影矩阵为正交投影
    camera->setProjectionMatrix(osg::Matrix::ortho2D(0, vp->width(), 0, vp->height()));

    //设置其观察矩阵为单位矩阵,且不改变,该相机永远显示,也不用参与拣选
    camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
    camera->setViewMatrix(osg::Matrix::identity());

    //只清空深度缓存,使得其显示内容可以以主相机为背景
    camera->setClearMask(GL_DEPTH_BUFFER_BIT);

    //最后渲染,因为需要以主相机显示的内容为背景
    camera->setRenderOrder(osg::Camera::POST_RENDER);

    //不需要响应事件
    camera->setAllowEventFocus(false);

    //绘制选择框
    osg::Geode* gnode = new osg::Geode;
    camera->addChild(gnode);

    gnode->addDrawable(g_geomSelectBox);
    //设置透明
    osg::StateSet* ss = gnode->getOrCreateStateSet();
    ss->setMode(GL_BLEND, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
    ss->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
    ss->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);

    //设置顶点
    osg::Vec3Array* vertices = new osg::Vec3Array;
    float depth = -0.1;
    vertices->push_back(osg::Vec3(0, 0, depth));
    vertices->push_back(osg::Vec3(100, 0, depth));
    vertices->push_back(osg::Vec3(100, 100, depth));
    vertices->push_back(osg::Vec3(0, 100, depth));
    g_geomSelectBox->setVertexArray(vertices);

    //设置颜色
    osg::Vec4Array* color = new osg::Vec4Array;
    color->push_back(osg::Vec4(0.8, 0.8, 0.8, 0.2));
    g_geomSelectBox->setColorArray(color, osg::Array::BIND_OVERALL);

    //绘制盒子
    g_geomSelectBox->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));

    return camera;
}

class MyEvent : public osgGA::GUIEventHandler
{
public:
    MyEvent() :osgGA::GUIEventHandler(),
        _xStart(0),
        _yStart(0)
    {
    }

    virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
    {
        //左ctrl按着,又鼠标点击,则进入绘制状态
        if (ea.getEventType() == ea.PUSH) //
        {
            //判断左CTRL键是否按下
            if (ea.getModKeyMask() == ea.MODKEY_LEFT_CTRL)
            {
                _xStart = ea.getX();
                _yStart = ea.getY();
                //清空之前绘制的结果,这里仅隐藏即可
                g_geomSelectBox->setNodeMask(NODE_HIDE);

                //清空点选内容,一切重新开始
                _pickArea.clear();
                ClearSelect();

                //返回真代表后续事件处理器包括操作器不再处理该事件,就实现了拖动时场景不动
                return true;
            }
        }


        //左ctrl点下时,进入到选择状态,开始绘制选择框,且操作器不再处理鼠标拖动事件
        if (ea.getEventType() == ea.DRAG) //鼠标拖动,拖动是鼠标按键按下的时候移动鼠标
        {
            //判断左CTRL键是否按下
            if (ea.getModKeyMask() == ea.MODKEY_LEFT_CTRL)
            {
                //开始绘制,调整顶点参数就可以
                g_geomSelectBox->setNodeMask(NODE_SHOW);

                //获取顶点、更新顶点
                osg::Vec3Array* vertices = (osg::Vec3Array*)g_geomSelectBox->getVertexArray();
                int xEnd = ea.getX();
                int yEnd = ea.getY();
                float depth = -0.1;
                vertices->at(0).set(_xStart, _yStart, depth);
                vertices->at(1).set(xEnd, _yStart, depth);
                vertices->at(2).set(xEnd, yEnd, depth);
                vertices->at(3).set(_xStart, yEnd, depth);

                //重绘
                g_geomSelectBox->dirtyDisplayList();

                int xMin = _xStart > ea.getX() ? ea.getX() : _xStart;
                int xMax = _xStart < ea.getX() ? ea.getX() : _xStart;
                int yMin = _yStart > ea.getY() ? ea.getY() : _yStart;
                int yMax = _yStart < ea.getY() ? ea.getY() : _yStart;

                //将框选的区域压入到_pickArea
                for (int i = xMin; i <= xMax; i+=5)
                {
                    for (int j = yMin; j <= yMax; j+=5)
                    {
                        if (!isPick(i, j))
                        {
                            _pickArea.push_back(osg::Vec2i(i, j));
                            //进行pick
                            osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa);
                            osgUtil::LineSegmentIntersector::Intersections intersections;
                            //只pick和NODE_PICK相&得true的
                            if (view->computeIntersections(i, j, intersections, NODE_PICK))
                            {
                                for (osgUtil::LineSegmentIntersector::Intersections::iterator iter = intersections.begin();
                                    iter != intersections.end(); iter++)
                                {
                                    osg::NodePath np = iter->nodePath;

                                    MySphere* ms = dynamic_cast<MySphere*>(np.at(np.size() - 1));
                                    if (ms)
                                    {
                                        ms->setSelect(true);
                                    }
                                }
                            }
                        }

                    }
                }



                //返回真代表后续事件处理器包括操作器不再处理该事件,就实现了拖动时场景不动
                return true;
            }
        }

        return false;
    }

    //看一个点是不是pick过,返回true是已经pick过
    bool isPick(int x, int y)
    {
        for (int i = 0; i < _pickArea.size(); i++)
        {
            if ((_pickArea.at(i).x() == x) && (_pickArea.at(i).y() == y))
            {
                return true;
            }
        }

        return false;
    }

    int _xStart, _yStart;
    
    //鼠标按下去的时候,再拖动,就把框选的区域内的点都压进去,用于pick
    std::vector<osg::Vec2i> _pickArea;
};


int main()
{
    osgViewer::Viewer viewer;

    osg::Group* root = new osg::Group;

    root->addChild(BuildScene());

    viewer.setSceneData(root);
    viewer.realize();

    //realize之后,上下文已经被初始化,可以获得视口大小
    //在此处获得视口大小是为了创建HUD时使其视口大小与创建的一致
    osg::Viewport* vp = viewer.getCamera()->getViewport();
    root->addChild(createHUD(vp));

    viewer.addEventHandler(new MyEvent);

    return viewer.run();
}

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片