浅谈OSG的默认视点方向

news2024/11/28 0:41:29

目录

1. 前言

2. OPenGL坐标系和OSG坐标系

3. 默认视点有关的几个案例

4. 视点操作

4.1. 视点调整

4.2. 左右转动

4.3. 向前走

5. 总结

6. 参考资料


1. 前言

       在OSG开发中,对视点的理解透彻是必须可少的,特别是在进行自定义操控器类的开发中,对视点的深刻理解,更是重要,否则自定义的操控器功能可能会不正常、不会按预想的那样。关于怎么编写自定义操控器,可参考:自定义一个简单的操控器类。

        默认视点方向为何如此重要?因为旋绕、移动操作最开始是建立在默认视点基础上,即以默认视点为基础进行旋转、移动,从而在视觉上让人觉得场景在变化。如果默认视点方向理解错了,则后续的旋绕、移动操作都是错的,从而导致场景的旋转、移动不是预想的。

2. OPenGL坐标系和OSG坐标系

     OPenGL坐标系和OSG坐标系都是右手坐标系。OPenGL坐标系如下:

图1  

  • 红色表示X轴正半轴,朝向右边。
  • 绿色表示Y轴正半轴,方向从底部垂直指向顶部。
  • 蓝色表示Z轴正半轴,方向垂直屏幕,由屏幕内部指向屏幕外部。

OSG的坐标系如下:

图2 

  • 红色表示X轴正半轴,朝向右边。
  • 绿色表示Y轴正半轴,方向指向屏幕内部。
  • 蓝色表示Z轴正半轴,方向由屏幕底部指向顶部。

可以看到OSG坐标系其实是OSG在其内核中将OPenGL坐标系绕X轴正半轴顺时针旋转了90°。

3. 默认视点有关的几个案例

案例1:

如下代码:

#include <osgDB/readFile>
#include<osgViewer/Viewer>
int main()
{
	osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
	osg::ref_ptr<osg::Group> root = new osg::Group;

	osg::ref_ptr<osg::Node> node = osgDB::readNodeFile("cow.osg");
	root->addChild(node);
	viewer->setSceneData(root.get());

	viewer->run();
}

代码段1 

加载了一个奶牛模型到视景器中:

图3 

注意:在用osg进行场景显示时,必须至少存在一个操控器类对象,即必须至少存在一个从osgGA::CameraManipulator派生的子类对象,且必须通过如下函数

viewer->setCameraManipulator(操控器类对象);

代码段2 

将该操控器类子对象设置到视景器中,否则osg程序就会因为没有操控器类对象而崩溃终止。读者可能会问了:上面代码没见设置操控器到视景器中啊!通过跟踪

viewer->run();

代码段3 

这句代码后到Viewer::run函数:

int Viewer::run()
{
    if (!getCameraManipulator() && getCamera()->getAllowEventFocus())
    {
        setCameraManipulator(new osgGA::TrackballManipulator());
    }

    setReleaseContextAtEndOfFrameHint(false);

    return ViewerBase::run();
}

代码段4 

才明白osg检测到外层如果没有设置操控器,则就为我们设置一个osgGA::TrackballManipulator即跟踪球操控器。正是在因为设置了跟踪球操控器,才导致程序一起来,我们就能看到奶牛位于视景器中心,视点由Y轴负半轴看向正半轴。即跟踪球操控器将默认视点方向改变为由Y轴负半轴看向正半轴。那么最开始的默认视点方向是朝向哪里?即如果不用跟踪球操控器或不人为调整默认视点方向,默认视点朝向哪个方向呢?

:关于跟踪球操控器是如何将场景调整到视景器中心、如何调整默认视点方向的,请参考:

          osgGA::CameraManipulator类computeHomePosition函数分析博文。

案例2:

#include <osgDB/readFile>
#include<osgViewer/Viewer>
#include<osgGA/AnimationPathManipulator>
osg::Node* createBase(const osg::Vec3 center, float radius)
{
    osg::Group* root = new osg::Group;
  
    int numTilesX = 10;
    int numTilesY = 10;

    float width = 2 * radius;
    float height = 2 * radius;

    osg::Vec3 v000(center - osg::Vec3(width * 0.5f, height * 0.5f, 0.0f));
    osg::Vec3 dx(osg::Vec3(width / ((float)numTilesX), 0.0, 0.0f));
    osg::Vec3 dy(osg::Vec3(0.0f, height / ((float)numTilesY), 0.0f));

    // fill in vertices for grid, note numTilesX+1 * numTilesY+1...
    osg::Vec3Array* coords = new osg::Vec3Array;
    int iy;
    for (iy = 0; iy <= numTilesY; ++iy)
    {
        for (int ix = 0; ix <= numTilesX; ++ix)
        {
            coords->push_back(v000 + dx * (float)ix + dy * (float)iy);
        }
    }

    //Just two colours - black and white.
    osg::Vec4Array* colors = new osg::Vec4Array;
    colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f)); // white
    colors->push_back(osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); // black

    osg::ref_ptr<osg::DrawElementsUShort> whitePrimitives = new osg::DrawElementsUShort(GL_QUADS);
    osg::ref_ptr<osg::DrawElementsUShort> blackPrimitives = new osg::DrawElementsUShort(GL_QUADS);

    int numIndicesPerRow = numTilesX + 1;
    for (iy = 0; iy < numTilesY; ++iy)
    {
        for (int ix = 0; ix < numTilesX; ++ix)
        {
            osg::DrawElementsUShort* primitives = ((iy + ix) % 2 == 0) ? whitePrimitives.get() : blackPrimitives.get();
            primitives->push_back(ix + (iy + 1) * numIndicesPerRow);
            primitives->push_back(ix + iy * numIndicesPerRow);
            primitives->push_back((ix + 1) + iy * numIndicesPerRow);
            primitives->push_back((ix + 1) + (iy + 1) * numIndicesPerRow);
        }
    }

    // set up a single normal
    osg::Vec3Array* normals = new osg::Vec3Array;
    normals->push_back(osg::Vec3(0.0f, 0.0f, 1.0f));

    osg::Geometry* geom = new osg::Geometry;
    geom->setVertexArray(coords);

    geom->setColorArray(colors, osg::Array::BIND_PER_PRIMITIVE_SET);

    geom->setNormalArray(normals, osg::Array::BIND_OVERALL);

    geom->addPrimitiveSet(whitePrimitives.get());
    geom->addPrimitiveSet(blackPrimitives.get());

    osg::Geode* geode = new osg::Geode;
    geode->addDrawable(geom);
    geode->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
    root->addChild(geode);

    root->addChild(osgDB::readNodeFile("axes.osgt"));

    return root;
}

int main()
{
    osgViewer::Viewer viewer;
    
    auto pAnimationPath = new osg::AnimationPath;
    osgGA::AnimationPathManipulator* apm = new 
    osgGA::AnimationPathManipulator(pAnimationPath )
    osg::Vec3d eye, center, up;
    apm->getHomePosition(eye, center, up);
    std::cout << "eye pos:" << "x: " << eye.x() << "y: " << eye.y() << "z: " << eye.z() << std::endl;
    viewer.setCameraManipulator(apm);
    viewer.setSceneData(createBase(osg::Vec3(0.0, 0.0, 0.0), 20.0));

	viewer.run();
}

代码段5 

 上面的代码,以坐标原点为中心,分别在X、Y正负轴绘制了一个10 * 10的黑白嵌套的类似棋盘的地砖,并安装了动画路径操控器(关于动画路径操控器的详细描述,请参考:osg操控器之动画路径操控器osgGA::AnimationPathManipulator分析博文),本想得到如下结果:

图4

然而得到的却是如下结果:

图5 

 这显然不是我们想要的结果。造成这种现象的原因是:没有调整视点位置和方向,程序采用的是默认视点位置和方向,即默认观察点看向的方向没有看到场景,即没看到地砖那么最开始的默认视点方向是朝向哪里,默认视点位置位于何处?我们应该怎样调整视点位置和方向,才能看到场景地砖?

4. 视点操作

4.1. 视点调整

        从上面的打印可以看出:默认视点即_eye,其位置位于(0.0, -1.0, 0.0)处。现在首先要弄明白的是当_rotation = 0,0,0的时候,它朝哪?这是个默认朝向,然后我们根据它朝哪,我们才能做自己的操作。还是结合前面的黑白地砖例子来讲,视点默认朝向如下图:

图6 

其中红色是x轴,绿色是y轴,蓝色是z轴。可以看到_rotation = 0,0,0的时候,默认视点朝向z轴负方向,且头顶是y轴,右手是x轴。 现在要把它移向正常的朝向,期望像图4那样。对比默认朝向,只需要沿x轴正半轴顺时针旋转90度就可以了(关于顺、逆时针的旋转的具体介绍,请参考:左/右手坐标系绕不同轴顺时针旋转动不同的理解与总结)。因此我们在_rotation的时候这样定义了:

_rotation = osg::Vec3(osg::inDegrees(90.0), 0.0, 0.0);

代码段6 

理解了上面所讲的,更改代码段5中的main函数如下:

int main()
{
    osgViewer::Viewer viewer;

    osg::Quat rotate(osg::inDegrees(90.0), osg::X_AXIS);
    auto pAnimationPath = new osg::AnimationPath;
    pAnimationPath->insert(0.0, osg::AnimationPath::ControlPoint(osg::Vec3d(0.0, -18.0, 1.0), rotate));
    osgGA::AnimationPathManipulator* apm = new osgGA::AnimationPathManipulator(pAnimationPath);
    osg::Vec3d eye, center, up;
    apm->getHomePosition(eye, center, up);
    std::cout << "eye pos:" << "x: " << eye.x() << "y: " << eye.y() << "z: " << eye.z() << std::endl;
    viewer.setCameraManipulator(apm);
    viewer.setSceneData(createBase(osg::Vec3(0.0, 0.0, 0.0), 20.0));

	viewer.run();
}

代码段7 

  1. 先将默认视点方向绕x轴正半轴顺时针转动90°。
  2. 再将其在0s时刻即立马移动到(0.0, -18.0, 1.0)位置。

经过上述调整视点朝向和位置后,就可以看到图4的场景效果了。 

4.2. 左右转动

以下摘自杨石兴博客:

       没有什么特别的原因,旋转我们就不需要操作xy轴的旋转了,向左向右旋转在上图的基本上其实就是绕z轴旋转。这里要注意方向,比如绕z轴旋转45度,是看向这里:

图7 

图中示意的角度是45度,靠近z轴的红色箭头就是当前的朝向。注意旋转10度,是顺时针旋转10度,-10度是逆时针旋转10度。在事件处理中点q我们就加2度(顺时针),点e我们就减两度(逆时针)。代码如下: 

            if ((ea.getKey() == 'q') || (ea.getKey() == 'Q')) //右转
            {
                _rotation.z() += osg::inDegrees(2.0);
                if (_rotation.z() > osg::inDegrees(180.0))
                {
                    _rotation.z() -= osg::inDegrees(360.0);
                }
            }
            if ((ea.getKey() == 'E') || (ea.getKey() == 'e')) // 左转
            {
                _rotation.z() -= osg::inDegrees(2.0);
                if (_rotation.z() < osg::inDegrees(-180.0))
                {
                    _rotation.z() += osg::inDegrees(360.0);
                }
            }

代码段8 

4.3. 向前走

以下摘自杨石兴博客:

       向前走是点w,也是个大难题,容易把人绕晕乎。向前走不涉及z的值,只涉及xy的值。而具体往哪个方向走,将步长固定下来,则xy方向上的变化量就是与朝向有关系了,如下图所示:

图8 

       上图看仔细了啊,现在我们站在原点,要走向红线的另一头,红线的长度是步长,_eye的z值怎么走都不变,则如图走到红线另一头则x方向上的变化是绿线,y方向上的变化是黄线,图中蓝线的角度就是_rotation.z(),图上这个角度肯定是负的,因为是顺时针转了一点点。默认朝向是朝y轴正方向的,顺时针转了一点点就是负值,是蓝色的角度。已知蓝色的角度,和红色的步长,那求出来黄色和绿色就是分分钟的事情了。

     这里面在第一四象限可以用一个公式,在二三象限可以用一个公式。角度有正负、xy有正负、正逆时针旋转有正负,这三个正负搅和在一起,让你没有本文辅佐实难理清呀。拿第一象限来说,蓝色角度_rotation.z()在[0, 90]之间,stepSize * std::sin(_rotation.z()) 就是绿线。stepSize * std::cos(_rotation.z());就是黄线。慢慢理吧,以下是代码:

            if ((ea.getKey() == 'w') || (ea.getKey() == 'W'))//前进
            {
                float stepSize = 0.5;
                float zRot = _rotation.z();//[-180, 180]
                //判断朝向以xy为平面的哪个象限,注意默认是朝各Y轴正方向的,时不时就得提一下
                //第一象限||第四象限
                if (((zRot >= osg::inDegrees(0.0)) && (zRot <= osg::inDegrees(90.0)))|| ((zRot <= osg::inDegrees(180.0)) && (zRot >= osg::inDegrees(90.0))))
                {
                    _eye.x() += stepSize * std::sin(zRot);
                    _eye.y() += stepSize * std::cos(zRot);
                }
                else //二三象限
                {
                    _eye.x() += stepSize * std::sin(-zRot);
                    _eye.y() += stepSize * std::cos(-zRot);
                }           
            }

代码段9 

以下是所有的代码,在一个cpp文件中,直接拷走可用:

#include <osgViewer/viewer>
#include <osgDB/ReadFile>
#include <osg/Geode>
#include <osg/Geometry>
#include <osgGA/CameraManipulator>

class TravelCameraManipulator : public osgGA::CameraManipulator
{
public:
    TravelCameraManipulator()
    {
        //初始的场景是个20x20的棋盘,中心点在[0,0],xy的坐标范围是从[-10,10],z=0
        //设置_eye的出生点
        _eye = osg::Vec3(0.0, -8, 1.0);
        //这里很关键,_rotation=(0,0,0)的情况下视点会朝向哪里呢,这是个基准参考量,后面的旋转都是从
        //这里来,所以务必要弄清楚,000时的朝向是Z轴负方向,头顶向Y轴正方向,自然右手边就是X轴正方向
        //在简书的文章里有图,简书搜杨石兴,《osg3.6.5最短的一帧》等找找
        //我们要想让视角转着朝向前方,也即站在(0.0, -8, 1.0)看向(0,0,0),则只需要看向Y轴
        //正方向就可以,则只需要x轴方向逆时针转90度,则出生就是朝向这里了
        //用户可以自己修改这个值感受一下
        _rotation = osg::Vec3(osg::inDegrees(90.0), 0.0, 0.0);
        
    }

    //这三个纯虚函数本例不会使用
    virtual void setByMatrix(const osg::Matrixd& matrix) {};
    virtual void setByInverseMatrix(const osg::Matrixd& matrix) {};
    virtual osg::Matrixd getMatrix() const { return osg::Matrix::identity(); };

    //最关键的是这个,这个返回的就是ViewMatrix
    virtual osg::Matrixd getInverseMatrix() const
    {
        return osg::Matrix::inverse(osg::Matrix::rotate(_rotation.x(), osg::X_AXIS, _rotation.y(), osg::Y_AXIS,
            _rotation.z(), osg::Z_AXIS) * osg::Matrix::translate(_eye));
    };

    //事件处理,我们要点击A就围着Z轴顺时针转动,点D就逆时针转动,转的时候始终朝0 0 0 点看着
    virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us)
    {
        if (ea.getEventType() == osgGA::GUIEventAdapter::KEYDOWN)
        {
            //若是A键
            if ((ea.getKey() == 'q') || (ea.getKey() == 'Q')) // 右转
            {
                _rotation.z() += osg::inDegrees(2.0);
                if (_rotation.z() > osg::inDegrees(180.0))
                {
                    _rotation.z() -= osg::inDegrees(360.0);
                }
            }
            if ((ea.getKey() == 'E') || (ea.getKey() == 'e')) // 左转
            {
                _rotation.z() -= osg::inDegrees(2.0);
                if (_rotation.z() < osg::inDegrees(-180.0))
                {
                    _rotation.z() += osg::inDegrees(360.0);
                }
            }
            if ((ea.getKey() == 'w') || (ea.getKey() == 'W'))//前进
            {
                float stepSize = 0.5;
                float zRot = _rotation.z();//[-180, 180]
                //判断朝向以xy为平面的哪个象限,注意默认是朝各Y轴正方向的,时不时就得提一下
                //第一象限||第四象限
                if (((zRot >= osg::inDegrees(0.0)) && (zRot <= osg::inDegrees(90.0)))|| ((zRot <= osg::inDegrees(180.0)) && (zRot >= osg::inDegrees(90.0))))
                {
                    _eye.x() += stepSize * std::sin(zRot);
                    _eye.y() += stepSize * std::cos(zRot);
                }
                else //二三象限
                {
                    _eye.x() += stepSize * std::sin(-zRot);
                    _eye.y() += stepSize * std::cos(-zRot);
                }           
            }
        }
        return false;
    }

    //视点位置
    osg::Vec3d              _eye;
    //视点朝向
    osg::Vec3d              _rotation;
};

osg::Node* createBase(const osg::Vec3 center, float radius)
{
    osg::Group* root = new osg::Group;

    int numTilesX = 10;
    int numTilesY = 10;

    float width = 2 * radius;
    float height = 2 * radius;

    osg::Vec3 v000(center - osg::Vec3(width * 0.5f, height * 0.5f, 0.0f));
    osg::Vec3 dx(osg::Vec3(width / ((float)numTilesX), 0.0, 0.0f));
    osg::Vec3 dy(osg::Vec3(0.0f, height / ((float)numTilesY), 0.0f));

    // fill in vertices for grid, note numTilesX+1 * numTilesY+1...
    osg::Vec3Array* coords = new osg::Vec3Array;
    int iy;
    for (iy = 0; iy <= numTilesY; ++iy)
    {
        for (int ix = 0; ix <= numTilesX; ++ix)
        {
            coords->push_back(v000 + dx * (float)ix + dy * (float)iy);
        }
    }

    //Just two colours - black and white.
    osg::Vec4Array* colors = new osg::Vec4Array;
    colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f)); // white
    colors->push_back(osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); // black

    osg::ref_ptr<osg::DrawElementsUShort> whitePrimitives = new osg::DrawElementsUShort(GL_QUADS);
    osg::ref_ptr<osg::DrawElementsUShort> blackPrimitives = new osg::DrawElementsUShort(GL_QUADS);

    int numIndicesPerRow = numTilesX + 1;
    for (iy = 0; iy < numTilesY; ++iy)
    {
        for (int ix = 0; ix < numTilesX; ++ix)
        {
            osg::DrawElementsUShort* primitives = ((iy + ix) % 2 == 0) ? whitePrimitives.get() : blackPrimitives.get();
            primitives->push_back(ix + (iy + 1) * numIndicesPerRow);
            primitives->push_back(ix + iy * numIndicesPerRow);
            primitives->push_back((ix + 1) + iy * numIndicesPerRow);
            primitives->push_back((ix + 1) + (iy + 1) * numIndicesPerRow);
        }
    }

    // set up a single normal
    osg::Vec3Array* normals = new osg::Vec3Array;
    normals->push_back(osg::Vec3(0.0f, 0.0f, 1.0f));

    osg::Geometry* geom = new osg::Geometry;
    geom->setVertexArray(coords);

    geom->setColorArray(colors, osg::Array::BIND_PER_PRIMITIVE_SET);

    geom->setNormalArray(normals, osg::Array::BIND_OVERALL);

    geom->addPrimitiveSet(whitePrimitives.get());
    geom->addPrimitiveSet(blackPrimitives.get());

    osg::Geode* geode = new osg::Geode;
    geode->addDrawable(geom);
    geode->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
    root->addChild(geode);

    root->addChild(osgDB::readNodeFile("axes.osgt"));

    return root;
}

int main()
{
    osgViewer::Viewer viewer;

    viewer.setCameraManipulator(new TravelCameraManipulator);
    viewer.setSceneData(createBase(osg::Vec3(0.0, 0.0, 0.0), 10.0));

    return viewer.run();
}

代码段10 

 注意:上面转动时,发现z轴也跟踪转动跑掉了,这是因为没固定视点和被观察物体中心点的距离导致的,如果想保持z轴固定不动,请参见:自定义一个简单的操控器类博文。

5. 总结

      结合上面的分析,我们得出结论:

  • OSG在内部已经将OPenGL的坐标系绕X轴正半轴顺时针旋转了90°。
  • OSG并没对视点进行旋转或平移,仅仅只是某些自带的操控器(如:跟踪球操控器)实现了对视点进行调整,以使其朝向场景,但大部分是没有调整默认视点方向的,大多数情况下,调整默认视点方向是程序员的责任。
  • 默认视点位置位于(0.0, -1.0, 0.0),朝向z轴负方向(屏幕向里),且头顶是y轴,右手是x轴,如果场景不可见,则需调整视点位置和朝向。

6. 参考资料

 【1】:第9节 实例-最简单的第一人称漫游操作器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1160385.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【组件】身份证卡片

效果展示 组件代码 <div class"cardOneRoot"><img :src"itemInfo.psnPicUrl ? itemInfo.psnPicUrl : " alt""/><div class"infoDiv" v-if"itemInfo.noInfo 1"><div v-for"(item2, index2) in …

“第五十七天”

不出意外的话&#xff0c;机组这周也就过完了&#xff0c;不过就真的是简单过一遍&#xff0c;先简单有个印象吧&#xff0c;这样看的话还是放假之前还是有机会把四大件都过一遍的。 这个思路&#xff0c;真的太神了&#xff0c;当然也是自己太弱了&#xff0c;就这我还为自己想…

VulnHub DC-3

一、信息收集 1.nmap扫描 只开放了80端口&#xff0c;所以只能从80入手 访问web页面 提示&#xff1a;只有一个flag&#xff0c;并且只有一个入口 wappalyzer插件 知道站点使用Joomla框架 使用该框架扫描工具 2.Joomla工具 joomscan --help joomscan -u 192.168.103.192 -e…

HNU-计算机网络-实验1-应用协议与数据包分析实验(Wireshark)

计算机网络 课程基础实验一 应用协议与数据包分析实验(Wireshark) 计科210X 甘晴void 202108010XXX 一、实验目的&#xff1a; 通过本实验&#xff0c;熟练掌握Wireshark的操作和使用&#xff0c;学习对HTTP协议进行分析。 二、实验内容 2.1 HTTP 协议简介 HTTP 是超文本…

《算法通关村——缓存机制了解LRU实现》

《算法通关村——缓存机制了解LRU实现》 介绍 LRU是"Least Recently Used"&#xff08;最近最少使用&#xff09;的缓存机制&#xff0c;它是一种常用的缓存算法&#xff0c;用于管理缓存中的数据项。LRU缓存机制的基本思想是&#xff0c;当缓存达到其容量限制时&a…

ChatGPT对未来发展的影响?一般什么时候用到GPT

ChatGPT以其强大的自然语言处理能力对未来的发展具有重要影响。以下是ChatGPT的潜在影响和一般使用情况&#xff1a; 改善自然语言理解和生成&#xff1a;ChatGPT和类似的模型可以改善机器对人类语言的理解和生成。这将有助于改进各种应用领域&#xff0c;包括智能助手、聊天机…

功放电路有这么多种,哪种音质最好呀?

功放电路种类很多&#xff0c;首先得知道各个功放电路的特点。 第一种&#xff0c;A类功放&#xff08;甲类功放&#xff09;&#xff0c;在信号的整个周期内都不会出现电流截止(即停止输出)的一类放大器。 但是A类放大器工作时会产生高热&#xff0c;效率很低&#xff0c;优…

使用postman进行接口自动化测试

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

数据结构之栈的实现

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇: Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”…

干货分享,大厂内部压测方案设计!

01、为什么要做压测 1、什么是压力测试&#xff1f; 不断向被测对象施加压力&#xff0c;测试系统在压力情况下的表现。 2、压力测试的目的是什么&#xff1f; 测试得出系统的极限性能指标&#xff0c;从而给出合理的承诺值或者容量告警&#xff1b; 找出系统的性能瓶颈&a…

5.1 实体完整性

思维导图&#xff1a; 前言 第5章 数据库完整性笔记 定义&#xff1a; 完整性&#xff1a;确保数据的正确性和相容性。 正确性&#xff1a;数据与现实世界语义相符、反映实际状况。相容性&#xff1a;同一对象在数据库的不同关系表中数据逻辑上是一致的。 示例&#xff1a; 学…

【发表案例】2区正刊,网络安全、智能系统领域,2个月3天录用,11天见刊,16天检索!

计算机类SCIE 【期刊简介】IF&#xff1a;4.0-5.0&#xff0c;JCR2区&#xff0c;中科院3区 【检索情况】SCIE 在检&#xff0c;正刊 【征稿领域】提高安全性和隐私性的边缘/云的智能方法的研究&#xff0c;如数字孪生等 【截稿日期】2023.11.30 录用案例&#xff1a;2个月…

LCD驱动程序——Framebuffer应用编程

1.LCD 操作原理 在 Linux 系统中通过 Framebuffer 驱动程序来控制 LCD。Frame 是帧的意思&#xff0c;buffer 是缓冲的意思&#xff0c;这意味着 Framebuffer 就是一块内存&#xff0c;里面保存着一帧图像。Framebuffer 中保存着一帧图像的每一个像素颜色值&#xff0c;假设 L…

怎样利用 AI 大模型,辅助研发管理与效能提升?

AI 大模型已经逐渐渗透到各行各业的应用场景中&#xff0c;在软件研发领域也不例外。在软件研发领域&#xff0c;从需求分析到软件设计&#xff0c;从软件开发到测试&#xff0c;以及最后发布上线&#xff0c;AI 在各个环节都发挥着重要作用。10 月 21 日&#xff0c;思码逸首席…

react条件渲染

目录 前言 1. 使用if语句 2. 使用三元表达式 3. 使用逻辑与操作符 列表渲染 最佳实践和注意事项 1. 使用合适的条件判断 2. 提取重复的逻辑 3. 使用适当的key属性 总结 前言 在React中&#xff0c;条件渲染指的是根据某个条件来决定是否渲染特定的组件或元素。这在构…

图解刘润2023年度演讲--进化的力量思维导图精华

大家好&#xff0c;我是老原。 周末&#xff0c;商业顾问刘润发表了年度演讲&#xff1a;《进化的力量&#xff1a;寒武纪大爆发》。 这两天出差期间&#xff0c;陆陆续续看完了这个长达4小时的演讲&#xff0c;梳理了2023年到底发生了些什么&#xff0c;现在的环境如何…… …

技术阅读周刊第三期

技术阅读周刊&#xff0c;每周更新。 历史更新 20231013&#xff1a;第一期20231022&#xff1a;第二期 Understanding The Linux TTY Subsystem URL: https://ishuah.com/2021/02/04/understanding-the-linux-tty-subsystem/本文讲解了 Linux TTY 的历史故事和来源。 TTY 是 t…

HNU-算法设计与分析-讨论课1

第一次小班讨论 &#xff08;以组为单位&#xff0c;每组一题&#xff0c;每组人人参与、合理分工&#xff0c;ppt中标记分工&#xff0c;尽量都有代码演示&#xff09; 1.算法分析题 2-10、2-15(要求&#xff1a;有ppt&#xff08;可代码演示&#xff09;) 2.算法实现题 2-4、…

蓝桥杯(C++ 扫雷)

题目&#xff1a; 思想&#xff1a; 1、遍历每个点是否有地雷&#xff0c;有地雷则直接返回为9&#xff0c;无地雷则遍历该点的周围八个点&#xff0c;计数一共有多少个地雷&#xff0c;则返回该数。 代码&#xff1a; #include<iostream> using namespace std; int g[…

【Java 进阶篇】Java中的响应输出字节数据

在Java Web应用程序开发中&#xff0c;处理响应是一个常见的任务。有时&#xff0c;您可能需要向客户端发送字节数据&#xff0c;而不仅仅是文本或HTML内容。这可以用于传输各种内容&#xff0c;如图像、文件、视频等。本文将详细介绍如何在Java中使用Response对象输出字节数据…