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

2023-11-07

本节内容

结合上一节,我们把框选这个功能给完善了,如下,白色的是我点击左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();
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

第37.2节 框选-框选场景中的物体 的相关文章

随机推荐

  • 深度学习学习笔记(一):深度学习在图像和视频的应用

    深度学习在图像和视频的应用 文章目录 深度学习在图像和视频的应用 一 图像 视频处理 1 超分辨率问题 2 图像去噪 3 图像增强 3 1 弱光照增强 3 2 动态范围增强 4 质量评价 二 图像 视频压缩 2 1 传统压缩编码框架 2 2
  • java的BigDecimal也会存在丢失精度的问题

    先说结论 务必使用BigDecimal valueOf 1 01 或者使用 new BigDecimal 1 01 而不要使用new BigDecimal 1 01 查看源码可以知道 BigDecimal valueOf double va
  • 七校联合NewStarCTF 公开赛赛道WEEK2 web wp

    也不知道是不是公开赛和内部赛是不是同一套题 week1的题挺简单的 这里小记一下week2的题目 如有侵权立刻删除 Word For You 2 Gen 这题很简单就带过一下吧 报错注入就行 1 updatexml 1 concat 0x7
  • 数据挖掘 决策树算法 ID3 通俗演绎

    决策树是对数据进行分类 以此达到 预测的目的 该决策树方法先根据训练集数据形成决策树 如果该树不能对所有对象给出正确的分类 那么选择一些例外加入到训练集数据中 重复该过程一直到形成正确的决策集 决策树代表着决策集的树形结构 决策树由决策结点
  • Openwrt下设置程序开机自动启动

    转自http www cnyubin com p 364 今天在使用Openwrt时 需要将scp到上面的程序设置为开机自动启动 按照Linux下设置自动启动的方法并不有效 后来在官方wiki下发现了介绍文章 具体可见Openwrt下htt
  • Epoll图解

    图解 Epoll怎么实现的 51CTO COM epoll详解 Ineffable 的博客 CSDN博客 epoll详解
  • Nginx健康检查

    0 背景 服务治理的一个重要任务是感知服务节点变更 完成服务自动注册及异常节点的自动摘除 这就需要服务治理平台能够 及时 准确的感知service节点的健康状况 1 方案概述 Nginx 提供了三种HTTP服务健康检查方案供用户选择 1 T
  • 三面阿里被挂,竟获内推名额,历经 5 面拿下口碑 offer...

    每一个互联网人心中都有一个大厂梦 百度 阿里巴巴 腾讯是很多互联网人梦寐以求的地方 而我也不例外 但是 BAT 等一线互联网大厂并不是想进就能够进的 它对人才的技术能力和学历都是有一定要求的 所以除了学历以外 我们的技术和能力都要过硬才行
  • win10微信打电话对方听不到你的声音,你能听到对方声音

    1 隐私权限 开始 设置 隐私 麦克风 更改 开启权限 2 禁用占用问题 右键电脑右下角小喇叭 声音 声音控制面板 录制 分别右键下面的几个选项 启用麦克风 立体声混音等 然后分别双击下面的麦克风 立体声等选项 高级 独占前面的v取消 确定
  • 拯救小tim【最短路】

    题目链接 这里有一个坑点 譬如说 我们从S出发的时间 不是刚好卡着第一个的 起始点 没准出发的第一步 没有卡起始点 而是在后面的到达其他点的时候卡了起始点这样的情况 所以我们应该从0 max BegTim的来枚举起点时间 然后跑Dijkst
  • javamail发送接收的简单demo

    目录结构 首先引入文件
  • 23考研重大软院数一英一391分经验帖

    今年这情况之后 所有前人的经验帖作废 前言 本校本专业生一战上岸 属于考研界难度最低的一档 今年有个初试439的怪物 属于是蚌了 第二名也有419 第三名就断档了 我初试第五 政治78 英一75 数一115 专业课123 总分391分 可以
  • [个人笔记] vCenter设置时区和NTP同步

    VMware虚拟化 运维篇 第三章 vCenter设置时区和NTP同步 VMware虚拟化 运维篇 系列文章回顾 vCenter设置时区和NTP同步 附加 ESXi设置alias 参考链接 系列文章回顾 第一章 vCenter给虚机添加RD
  • 鼓励参与计算机考试宣传标语,考试宣传标语

    考试标语 1 遵守考场纪律 维护知识尊严 2 提倡诚信做人 纯洁校园风气 3 考前不慌不乱 考时沉着应对 考后杜绝议论 4 用心看卷 专心答题 细心复查 5 我自信 我成功 6 与诚信携手同行与舞弊挥手作别 7 怀轻松心情进考场 带胜利喜悦
  • 安全https,dns笔记整理

    一 https HTTP 是用于从万维网 WWW World Wide Web 服务器传输超文本到本地浏览器的传送协议 他是一种应用层协议 是基于 TCP IP 通信协议来传递数据的 但他不安全 以明文传递的方式 容易被他人盗取篡改信息 H
  • java 提取存在逗号和小数点的字符串中的数字

    可以使用正则表达式来处理 以下是一个示例代码 可以提取字符串中可能包含逗号和小数点的数字 import java util regex Matcher import java util regex Pattern public class
  • Arduino和Python实时监督控制和数据采集系统(SCADA)

    本文 将向您展示如何设置环境温度信号 该信号将通过计算机上的实时仪表板记录和可视化数据 硬件设计 首先 我们将使用Arduino Uno开发板从红外温度计读取温度值 如上所示连接红外测温仪后 继续将以下程序上传到Arduino 要验证Ard
  • 如何在windows下切换node版本

    解决办法 1 用到某个版本对node重新卸载 安装对应的版本 2 使用nvm 很明显 第一种方法虽然也能解决node版本问题 但是太多麻烦 接下来介绍下nvm的安装使用 第一步 下载nvm并安装 推荐使用nvm setup zip nvm
  • ROS配置LTE第二链路实现故障自动切换

    本文几乎没有操作部分 主要是讲原理 设备是RB962 通过PPPoE方式上网 华为5577移动路由器配合电信日租卡作为第二链路备用 计划实现宽带正常时通过PPPoE线路上网 异常时通过LTE线路上网 PPPoE线路恢复正常时 流量回到PPP
  • 第37.2节 框选-框选场景中的物体

    目录 本节内容 实现要点 点选 性能 绘制球 本节内容 结合上一节 我们把框选这个功能给完善了 如下 白色的是我点击左CTRL 用鼠标左键在场景中拉的框 拉框的教程在第37 1节 框选 绘制框选框 拉完框后能够将场景中选择的物体置红 本节代