osg实现物体沿着控制点生成的Cardinal样条轨迹曲线运动

news2024/11/17 5:41:37

目录

1. 前言

2. 预备知识

3. 用osg实现三维Cardinal曲线

 3.1. 工具/ 原料

 3.2. 代码实现

4. 说明


1. 前言

       在设计矢量图案的时候,我们常常需要用到曲线来表达物体造型,单纯用鼠标轨迹绘制显然是不足的。于是我们希望能够实现这样的方法:通过设计师手工选择控制点,再通过插值得到过控制点(或在附近)的一条平滑曲线。在这样的需求下,样条曲线诞生了。简而言之,样条曲线是由多个多项式按比例系数组成的多项式函数,而比例系数是由控制点决定的。Hermite曲线、Cardinal曲线在平时的开发中,经常用于模拟运动物体的轨迹,如下:
 

以上是二维Cardinal曲线效果,如何用osg实现三维的Cardinal曲线,且物体(本例指球体)沿着Cardinal曲线运动呢?即像下面那样: 

即:

  1. 单击“拾取点”按钮,该按钮文字变为“关闭拾取点”,此时可以用鼠标在棋盘格上单击,点击的点用红色圆圈表示。
  2. 当所有的点都拾取完,单击“绘制”,可以绘制三维Cardinal曲线。
  3. 当绘制完三维Cardinal曲线后,再次用鼠标在棋盘格上单击,单击“绘制”,可以绘制新的三维Cardinal曲线。
  4. 单击“关闭拾取点”按钮,鼠标在棋盘格上单击时,无法拾取点。
  5. 调整阈值,可以更改曲线的圆滑度,使曲线从圆滑变为直线。
  6.  单击“运动”按钮,生成一个蓝色的球体,且该球体沿着Cardinal曲线运动。

2. 预备知识

      在阅读本博文之前,请阅读以下博文,否则很难看懂本博文。

  • [计算机动画] 路径曲线与运动物体控制(Cardinal样条曲线)。
  • 三次参数样条曲线与Cardinal曲线。
  • Qt实现三次样条Cardinal曲线。
  • osg实现三次样条Cardinal曲线。

3. 用osg实现三维Cardinal曲线

 3.1. 工具/ 原料

开发环境如下:

  • Qt 5.14.1。
  • Visual Studio 2022。
  • OpenSceneGraph 3.6.2。

 3.2. 代码实现

main.cpp 

#include "osgCardinal.h"
#include <QtWidgets/QApplication>
 
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    osgCardinal w;
    w.show();
    return a.exec();
}

myEventHandler.cpp

#include "myEventHandler.h"
#include<osgViewer/Viewer>
bool myEventHandler::handle(const osgGA::GUIEventAdapter& ea, 
                            osgGA::GUIActionAdapter& aa, 
                            osg::Object* obj, osg::NodeVisitor* nv)
{
    auto pViewer = dynamic_cast<osgViewer::Viewer*>(&aa);
    auto eventType = ea.getEventType();
    switch (eventType)
    {
        case GUIEventAdapter::PUSH:
        {
            if(_bPickPoint && (GUIEventAdapter::LEFT_MOUSE_BUTTON == ea.getButton()))
            {
                osgUtil::LineSegmentIntersector::Intersections intersections;
                auto bRet = pViewer->computeIntersections(ea, intersections);
                if (!bRet) // 判断是否相交
                {
                    return false;
                }

                auto iter = intersections.begin();  // 取相交的第1个点
                auto interPointCoord = iter->getLocalIntersectPoint();
                _pOsgCardinal->drawEllipse(interPointCoord);
                
            }
        }
        break;
    } // end switch

    return false;
}


void myEventHandler::setPickPoint(bool bPickPoint)
{
    _bPickPoint = bPickPoint;
}

myEventHandler.h

#ifndef MYEVENTHANDLER_H
#define MYEVENTHANDLER_H
#include<osgGA/GUIEventHandler>
#include<osgCardinal.h>
using namespace osgGA;

class myEventHandler:public GUIEventHandler
{
public:
    myEventHandler(osgCardinal* p) { _pOsgCardinal = p; }
public:

    void setPickPoint(bool bPickPoint);

private:
    virtual bool handle(const osgGA::GUIEventAdapter& ea, 
                        osgGA::GUIActionAdapter& aa, 
                        osg::Object* obj, osg::NodeVisitor* nv) override;


private:
    bool _bPickPoint{false};
    osgCardinal* _pOsgCardinal{nullptr};
};

#endif MYEVENTHANDLER_H

osgCardinal.h 

#pragma once

#include <QtWidgets/QWidget>
#include "ui_osgCardinal.h"
using std::list;

QT_BEGIN_NAMESPACE
namespace Ui { class osgCardinalClass; };
QT_END_NAMESPACE

class myEventHandler;

class osgCardinal : public QWidget
{
    Q_OBJECT

public:
    osgCardinal(QWidget *parent = nullptr);
    ~osgCardinal();

public:

    // 画点。以小圆表示
    void  drawEllipse(const osg::Vec3d& pt);

private:

    void motion();

    // 生成一个球
    osg::Geode* createSphere();

    void addBaseScene();

    osg::Geode* createGrid();

    void valueChanged(double dfValue);

    void startDraw();

    void pickPoint();

    void clear();

    // 计算MC矩阵
    void calMcMatrix(double s);

    // 压入头部和尾部两个点,用于计算
    void pushHeadAndTailPoint();

    // 画Cardinal曲线
    void drawCardinal();

    void drawLines(osg::Vec3Array* pVertArray);

    // 创建球动画路径
    osg::AnimationPath* createSphereAnimationPath();

    // 插入控制点到动画路径
    void insertCtrlPoint(const osg::Vec3d&currrentPoint , const osg::Vec3d& nextPoint, osg::AnimationPath* sphereAnimationPath, double time);
private:
    Ui::osgCardinalClass *ui;
    myEventHandler*_myEventHandler{nullptr};
    bool _startPickPoint{false};
    bool _lastPointHasPoped{ false }; // 最后一个点是否被弹出(删除)
    bool _hasDrawed{ false }; // 以前是否绘制过Cardinal曲线
    double _dfMcMatrix[4][4];
    list<osg::Vec3d> _lstInterPoint;
    osg::Vec3Array*_pVertArray{ nullptr };          // 用于保存绘制Cardinal曲线的点的容器
    osg::Geometry* _pCardinalCurveGemo{ nullptr }; // Cardinal曲线
    float _radius{0.2};
    osg::Geode* _pSphere{ nullptr };      // 球
    osg::AnimationPathCallback* _sphereAnimationPathCb{nullptr}; // 球动画路径回调函数
    double _twoPi{ 2 * 3.1415926 };
};

osgCardinal.cpp

#include "osgCardinal.h"
#include"myEventHandler.h"
#include<osg/MatrixTransform>
#include<osg/PositionAttitudeTransform>
#include<osg/PolygonMode>
#include<osg/LineWidth>
#include<osg/ShapeDrawable>
#include<osg/PolygonOffset>
#include<vector>
using std::vector;

osgCardinal::osgCardinal(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::osgCardinalClass())
{
    ui->setupUi(this);

	setWindowState(Qt::WindowMaximized);

	addBaseScene();

    ui->doubleSpinBox->setMinimum(0);
    ui->doubleSpinBox->setMaximum(1);
    ui->doubleSpinBox->setValue(0.5);
    ui->doubleSpinBox->setSingleStep(0.1);

    connect(ui->doubleSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &osgCardinal::valueChanged);
    connect(ui->startDrawBtn, &QAbstractButton::clicked, this, &osgCardinal::startDraw);
    connect(ui->clearBtn, &QAbstractButton::clicked, this, &osgCardinal::clear);
    connect(ui->pickPointBtn, &QAbstractButton::clicked, this, &osgCardinal::pickPoint);
    connect(ui->motionBtn, &QAbstractButton::clicked, this, &osgCardinal::motion);
    calMcMatrix(0.5);
}

osgCardinal::~osgCardinal()
{
    delete ui;
}

// 生成一个球
osg::Geode* osgCardinal::createSphere()
{
    if (_lstInterPoint.size() < 2)
    {
        return nullptr;
    }

    auto it = _lstInterPoint.begin(); 
    ++it; // 越过人为插入的控制点
    auto center = osg::Vec3(0, 0, 0)/**it*/;
    center.z() += _radius;
    auto pSphere = new osg::Sphere(center, _radius);

    auto pSphereGeode = new osg::Geode();
    auto pTlh = new osg::TessellationHints();
    pTlh->setDetailRatio(0.5);
    auto pSphereDrawable = new osg::ShapeDrawable(pSphere, pTlh);
    pSphereDrawable->setColor(osg::Vec4(0.0, 0.0, 1.0, 1.0));
    pSphereGeode->addDrawable(pSphereDrawable);

    return pSphereGeode;
}

// 插入控制点到动画路径
void osgCardinal::insertCtrlPoint(const osg::Vec3d& currrentPoint, const osg::Vec3d&nextPoint , osg::AnimationPath* sphereAnimationPath, double time)
{
    auto angle = atan2f((nextPoint.y() - currrentPoint.y()), (nextPoint.x() - currrentPoint.x()));
    if (angle < 0)
    {
        angle = _twoPi + angle;
    }

    osg::Quat quat(angle, osg::Vec3(0, 0, 1));
    osg::AnimationPath::ControlPoint ctrlPoint(currrentPoint, quat);
    sphereAnimationPath->insert(time, ctrlPoint);
}

// 创建球动画路径
osg::AnimationPath* osgCardinal::createSphereAnimationPath()
{
    auto sphereAnimationPath = new osg::AnimationPath();
    sphereAnimationPath->setLoopMode(osg::AnimationPath::NO_LOOPING);
    double time = 0.0;
    for (auto iPointIndex = 0; iPointIndex < _pVertArray->size() - 2; ++iPointIndex)
    {
        auto currrentPoint = (*_pVertArray)[iPointIndex];
        auto nextPoint = (*_pVertArray)[iPointIndex + 1];     // 下一个点
        insertCtrlPoint(currrentPoint, nextPoint, sphereAnimationPath, time);
        time += 0.02;
    }

    // 最后一个点
    auto currrentPoint = (*_pVertArray)[_pVertArray->size() - 1];
    auto prevPoint = (*_pVertArray)[_pVertArray->size() - 2];
    insertCtrlPoint(prevPoint, currrentPoint, sphereAnimationPath, time);

    return sphereAnimationPath;
}

void osgCardinal::motion()
{
    // Cardinal曲线还未生成
    if (nullptr == _pVertArray)
    {
        return;
    }

    auto pMatrixTransform = ui->osg_widget->getSceneData()->asGroup()->getChild(0)->asTransform()->asMatrixTransform();
  
    _pSphere = createSphere();

    auto sphereAnimationPath = createSphereAnimationPath();
    osg::ref_ptr<osg::PositionAttitudeTransform> pat = new osg::PositionAttitudeTransform();
    pat->setPosition((*_pVertArray)[0]); // 鼠标按下的第1个点
    pat->removeChild(_pSphere); // 删除上次生成的球
    _sphereAnimationPathCb = new osg::AnimationPathCallback(sphereAnimationPath, 0.0);

    if (nullptr != _sphereAnimationPathCb)
    {
        _pSphere->removeUpdateCallback(_sphereAnimationPathCb);
    }

    pat->setUpdateCallback(_sphereAnimationPathCb);
    pat->addChild(_pSphere);
    pMatrixTransform->addChild(pat);
}

void osgCardinal::valueChanged(double dfValue)
{
    auto s = (1 - dfValue) / 2.0;

    // 计算MC矩阵
    calMcMatrix(s);

    drawCardinal();
}

// 画点。以小圆表示
void osgCardinal::drawEllipse(const osg::Vec3d& pt)
{
    if (!_lastPointHasPoped && _hasDrawed && !_lstInterPoint.empty())
    {
        /* 在上次绘制Cardinal曲线时,通过pushHeadAndTailPoint()
        *  压入的尾部用户控制点去掉,以便在开始绘制函数(参见startDraw)中重新压入尾部用户控制点
        * ,便于绘制本次曲线
        */
        _lstInterPoint.pop_back();
        _lastPointHasPoped = true;
    }

    _lstInterPoint.emplace_back(pt);

    auto pGeometry = new osg::Geometry;

    // 注意:为了防止Z值冲突,为几何体设置多边形偏移属性。具体参见:https://blog.csdn.net/danshiming/article/details/133958200?spm=1001.2014.3001.5502
    auto pPgo = new osg::PolygonOffset();
    pPgo->setFactor(-1.0);
    pPgo->setUnits(-1.0);
    pGeometry->getOrCreateStateSet()->setAttributeAndModes(pPgo);
    auto pVertArray = new osg::Vec3Array;
    _radius = 0.2;
    auto twoPi = 2 * 3.1415926;
    for (auto iAngle = 0.0; iAngle < twoPi; iAngle += 0.001)
    {
        auto x = pt.x() + _radius * std::cosf(iAngle);
        auto y = pt.y() + _radius * std::sinf(iAngle);

        // 注意:适当增加点,否则和网格重合了,会导致圆形绘制不正常
        auto z = pt.z()/* + 0.001*/; 
        pVertArray->push_back(osg::Vec3d(x, y, z));
    }

    pGeometry->setVertexArray(pVertArray);
 
    auto pColorArray = new osg::Vec4Array;
    pColorArray->push_back(osg::Vec4d(1.0, 0.0, 0.0, 1.0));
    pGeometry->setColorArray(pColorArray/*, osg::Array::BIND_OVERALL*/);
    pGeometry->setColorBinding(osg::Geometry::BIND_OVERALL);

    pGeometry->addPrimitiveSet(new osg::DrawArrays(GL_POLYGON, 0, pVertArray->size()));
    auto pMatrixTransform =  ui->osg_widget->getSceneData()->asGroup()->getChild(0)->asTransform()->asMatrixTransform();
    pMatrixTransform->addChild(pGeometry);
}

void osgCardinal::pickPoint()
{
    _startPickPoint = !_startPickPoint;
    _myEventHandler->setPickPoint(_startPickPoint);
    if (_startPickPoint)
    {
        ui->pickPointBtn->setText(QString::fromLocal8Bit("关闭拾取点"));
    }
    else
    {
        ui->pickPointBtn->setText(QString::fromLocal8Bit("拾取点"));
    }
}

void osgCardinal::startDraw()
{
    if (nullptr != _pCardinalCurveGemo) // 如果以前绘制过Cardinal曲线
    {
        /* 在上次绘制Cardinal曲线时,通过pushHeadAndTailPoint()
        *  压入的头部点去掉,以重新压入头部用户控制的两个点
        * ,便于绘制本次曲线
        */
        if (_lstInterPoint.size() >= 0 ) 
        {
            _lstInterPoint.pop_front();
        }
    }

    pushHeadAndTailPoint();

    drawCardinal();

    _hasDrawed = true;
}

// 压入头部和尾部两个点,用于计算
void osgCardinal::pushHeadAndTailPoint()
{
    // 随便构造两个点
    auto ptBegin = _lstInterPoint.begin();
    auto x = ptBegin->x() + 20;
    auto y = ptBegin->y() + 20;
    auto z = ptBegin->z();
    _lstInterPoint.insert(_lstInterPoint.begin(), osg::Vec3d(x, y, z));

    auto ptEnd = _lstInterPoint.back();
    x = ptEnd.x() + 20;
    y = ptEnd.y() + 20;
    z = ptBegin->z();
    _lstInterPoint.insert(_lstInterPoint.end(), osg::Vec3d(x, y, z));
}


// 画Cardinal曲线
void osgCardinal::drawCardinal()
{
    if (_lstInterPoint.size() < 4)
    {
        return;
    }
    
    if (nullptr == _pVertArray)
    {
        _pVertArray = new osg::Vec3Array();
    }
    else
    {
        _pVertArray->clear();
    }

    auto iter = _lstInterPoint.begin();
    ++iter; // 第1个点(基于0的索引)
    _pVertArray->push_back(*iter);
    --iter;

    auto endIter = _lstInterPoint.end();
    int nIndex = 0;
    while (true)
    {
        --endIter;
        ++nIndex;
        if (3 == nIndex)
        {
            break;
        }
    }

    for (; iter != endIter; ++iter)
    {
        auto& p0 = *iter;
        auto& p1 = *(++iter);
        auto& p2 = *(++iter);
        auto& p3 = *(++iter);

        --iter;
        --iter;
        --iter;

        vector<osg::Vec3d>vtTempPoint;
        vtTempPoint.push_back(p0);
        vtTempPoint.push_back(p1);
        vtTempPoint.push_back(p2);
        vtTempPoint.push_back(p3);

        for (auto i = 0; i < 4; ++i)
        {
           vtTempPoint[i] = p0 * _dfMcMatrix[i][0]  + p1 * _dfMcMatrix[i][1] + p2 * _dfMcMatrix[i][2] + p3 * _dfMcMatrix[i][3];
        }

        float t3, t2, t1, t0;
        for (double t = 0.0; t < 1; t += 0.01)
        {
            t3 = t * t * t; t2 = t * t; t1 = t; t0 = 1;
            osg::Vec3d newPoint;
            newPoint =  vtTempPoint[0] * t3 + vtTempPoint[1] * t2 + vtTempPoint[2] * t1 + vtTempPoint[3] * t0;
            _pVertArray->push_back(newPoint);
        }
    }

    drawLines(_pVertArray);
}

void osgCardinal::drawLines(osg::Vec3Array* pVertArray)
{
    if (nullptr == _pCardinalCurveGemo)
    {
        _pCardinalCurveGemo = new osg::Geometry;

        auto pLineWidth = new osg::LineWidth(50);
        _pCardinalCurveGemo->getOrCreateStateSet()->setAttributeAndModes(pLineWidth);

        auto pColorArray = new osg::Vec4Array;
        pColorArray->push_back(osg::Vec4d(0.0, 1.0, 0.0, 1.0));
        _pCardinalCurveGemo->setColorArray(pColorArray/*, osg::Array::BIND_OVERALL*/);
        _pCardinalCurveGemo->setColorBinding(osg::Geometry::BIND_OVERALL);

        auto pMatrixTransform = ui->osg_widget->getSceneData()->asGroup()->getChild(0)->asTransform()->asMatrixTransform();
        pMatrixTransform->addChild(_pCardinalCurveGemo);
    }

     // 曲线可能变了,先删除上次的曲线
    _pCardinalCurveGemo->removePrimitiveSet(0);
    _pCardinalCurveGemo->setVertexArray(pVertArray);

    // 再用新点绘制新曲线
    _pCardinalCurveGemo->addPrimitiveSet(new osg::DrawArrays(GL_LINE_STRIP, 0, pVertArray->size()));
}

// 计算MC矩阵
void osgCardinal::calMcMatrix(double s)
{
    _dfMcMatrix[0][0] = -s, _dfMcMatrix[0][1] = 2 - s, _dfMcMatrix[0][2] = s - 2, _dfMcMatrix[0][3] = s;
    _dfMcMatrix[1][0] = 2 * s, _dfMcMatrix[1][1] = s - 3, _dfMcMatrix[1][2] = 3 - 2 * s, _dfMcMatrix[1][3] = -s;
    _dfMcMatrix[2][0] = -s, _dfMcMatrix[2][1] = 0, _dfMcMatrix[2][2] = s, _dfMcMatrix[2][3] = 0;
    _dfMcMatrix[3][0] = 0, _dfMcMatrix[3][1] = 1, _dfMcMatrix[3][2] = 0, _dfMcMatrix[3][3] = 0;
}

void osgCardinal::clear()
{
    _lstInterPoint.clear();
    _hasDrawed = false;
    _lastPointHasPoped = false;
}

osg::Geode* osgCardinal::createGrid()
{
    auto pGeode = new osg::Geode;

    auto pVertArray = new osg::Vec3Array;
    for (auto y = -10; y < 10; ++y)
    {
        for (auto x = -10; x < 10; ++x)
        {
            pVertArray->push_back(osg::Vec3d(x, y, 0.0));
            pVertArray->push_back(osg::Vec3d(x + 1, y, 0.0));
            pVertArray->push_back(osg::Vec3d(x + 1, y + 1, 0.0));
            pVertArray->push_back(osg::Vec3d(x, y + 1, 0.0));
        }
    }

    auto iSize = pVertArray->size();
    osg::DrawElementsUShort* pEle{ nullptr };
    osg::Geometry* pGeomerty{ nullptr };
    osg::Vec4Array* pColorArray{ nullptr };

    auto nQuardIndex = 0;
    bool bNewLineQuard = true;  // 新的一行四边形
    for (auto iVertIndex = 0; iVertIndex < iSize; ++iVertIndex)
    {
        if (0 == (iVertIndex % 4))
        {
            pEle = new osg::DrawElementsUShort(GL_QUADS);

            pGeomerty = new osg::Geometry;
            pGeomerty->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
            pGeomerty->addPrimitiveSet(pEle);
            pGeode->addDrawable(pGeomerty);

            pGeomerty->setVertexArray(pVertArray);
            pColorArray = new osg::Vec4Array();
            if (bNewLineQuard)
            {
                pColorArray->push_back(osg::Vec4d(1.0, 1.0, 1.0, 1.0));
            }
            else
            {
                pColorArray->push_back(osg::Vec4d(0.0, 0.0, 0.0, 1.0));
            }

            ++nQuardIndex;

            if (0 != (nQuardIndex % 20))
            {
                bNewLineQuard = !bNewLineQuard;
            }

            pGeomerty->setColorArray(pColorArray, osg::Array::Binding::BIND_PER_PRIMITIVE_SET);
        }

        pEle->push_back(iVertIndex);
    } // end for

    return pGeode;
}

void osgCardinal::addBaseScene()
{
    auto pAxis = osgDB::readRefNodeFile(R"(E:\osg\OpenSceneGraph-Data\axes.osgt)");
    if (nullptr == pAxis)
    {
        OSG_WARN << "axes node is nullpr!";
        return;
    }

    auto pRoot = new osg::Group();

    pRoot->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
    auto pMatrixRoot = new osg::MatrixTransform;
    auto pGrid = createGrid();
    pMatrixRoot->addChild(pGrid);
    pMatrixRoot->addChild(pAxis);
    pRoot->addChild(pMatrixRoot);

    pMatrixRoot->setMatrix(osg::Matrix::rotate(osg::inDegrees(60.0), osg::Vec3(1, 0, 0)));

    ui->osg_widget->setSceneData(pRoot);
    ui->osg_widget->setCameraManipulator(new osgGA::TrackballManipulator);
    ui->osg_widget->addEventHandler(new osgViewer::WindowSizeHandler);
    ui->osg_widget->addEventHandler(new osgViewer::StatsHandler);
    _myEventHandler = new myEventHandler(this);
    ui->osg_widget->addEventHandler(_myEventHandler);

    // 模拟鼠标滚轮朝向人滚动三次,以便场景离人显得更近些
    for (auto iLoop = 0; iLoop < 3; ++iLoop)
    {
        ui->osg_widget->getEventQueue()->mouseScroll(osgGA::GUIEventAdapter::SCROLL_DOWN);
    }  
}

4. 说明

       上述代码drawEllipse的函数中,通过构造osg::PolygonOffset对象,加入了多边形漂移,从而解决了Z冲突问题 ,否则绘制圆形不正常,具体参见:

如何避免osg绘制场景时因Z冲突导致绘制重影或不正常

        三维球的运动用到了osg中的动画路径和动画路径控制点,通过控制点来控制小球运动的轨迹,以保持小球始终沿着cardinal曲线运动。

        注意:要想使物体通过动画路径来进行动画,必须将物体作为子节点加入到osg::PositionAttitudeTransform或osg::MatrixTransform,然后调用它们的setUpdateCallback设置动画路径回调函数。直接在物体上调用是不会开启动画的,即如果在本例中像下面代码那样直接对球设置动画回调,则球不会运动:

_pSphere->setUpdateCallback(_sphereAnimationPathCb);

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

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

相关文章

ChatGPT AIGC 人工智能Excel计算业绩提成

公司业绩提成是一种激励措施,通常是指根据公司的业绩表现,对员工的绩效进行评估,然后给予相应的奖励或提成。 这种激励措施可以鼓励员工努力工作,提高团队的竞争力和生产效率,从而推动公司的业绩增长。 不过具体的提成计算方式和金额是根据公司政策和个人表现而定的。 …

[java进阶]——线程池的使用,自定义线程池

&#x1f308;键盘敲烂&#xff0c;年薪30万&#x1f308; 目录 一、线程池的存在意义 二、线程池的使用 2.1线程池的核心原理 2.2线程池的代码实现 三、自定义线程池 3.1线程池的参数详解 3.2线程池的执行原理 3.3灵魂两问 3.4线程池多大合适 3.5拒绝策略 一、线程池…

基于springboot实现分布式架构网上商城管理系统项目【项目源码+论文说明】计算机毕业设计

基于springcloud实现分布式架构网上商城演示 摘要 首先,论文一开始便是清楚的论述了系统的研究内容。其次,剖析系统需求分析,弄明白“做什么”,分析包括业务分析和业务流程的分析以及用例分析,更进一步明确系统的需求。然后在明白了系统的需求基础上需要进一步地设计系统,主要…

linux进程管理,一个进程的一生(喂饭级教学)

这篇文章谈谈linux中的进程管理。 一周爆肝&#xff0c;创作不易&#xff0c;望支持&#xff01; 希望对大家有所帮助&#xff01;记得收藏&#xff01; 要理解进程管理&#xff0c;重要的是周边问题&#xff0c;一定要知其然&#xff0c;知其所以然。看下方目录就知道都是干货…

AWTK实现汽车仪表Cluster/DashBoard嵌入式GUI开发(六):一个AWTK工程

一个AWTK工程基于C/C++编写,可以分为如下几步: 结合下图,看懂启动的部分。一般一个AWTK工程,需要实现哪些部分,就是其中开始之后白色的部分,比如调用main函数和gui_app_start时会做一些操作,比如asset_init和application_init时要做一些设置,还有退出的函数application…

《软件方法》2023版第1章(11)1.4.3 具体工作步骤

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 1.4 应用UML的建模工作流 1.4.3 使用UML建模的工作流步骤 图1-17中“工件形式”一列所列出的图就是本书推荐的在建模工作流ABCD中的UML用法&#xff0c;我用活动图进一步表示建模的步…

(三)(Driver)驱动开发之双机调试环境搭建及内核驱动的运行

文章目录 1. 驱动开发环境搭建2. 驱动开发新建项目及项目属性配置和编译3. 双机调试环境搭建3.1 安装虚拟机VMware3.2 配置Dbgview.exe工具3.3 基于Windbg的双机调试 4. 内核驱动的运行4.1 临时关闭系统驱动签名校验4.2 加载驱动 1. 驱动开发环境搭建 请参考另一篇:https://bl…

Mysql中MyISAM和InnoDB 引擎的区别

MyISAM 和 InnoDB 都是 Mysql 里面的两个存储引擎。 在 Mysql 里面&#xff0c;存储引擎是可以自己扩展的&#xff0c;它的本质其实是定义数据存储的方式以及数据读取的实现逻辑。而不同存储引擎本身的特性&#xff0c;使得我们可以针对性的选择合适的引擎来实现不同的业务场景…

初识Java 15-1 文件

目录 文件和目录路径 选择Path的片段 分析Path 添加或删除路径片段 目录 文件系统 监听Path 查找文件 读写文件 本笔记参考自&#xff1a; 《On Java 中文版》 更多详细内容请查看官方文档。 Java 7优化了Java的I/O编程&#xff0c;具体的表现就是java.nio.file。其中…

【王道代码】【2.3链表】d3

关键字&#xff1a; 奇偶序号拆分、a1&#xff0c;b1&#xff0c;a2&#xff0c;b2...an&#xff0c;bn拆分a1&#xff0c;a2...&#xff0c;bn&#xff0c;...b2&#xff0c;b1、删除相同元素

以人物画像谈测试员如何半道介入一个新项目

最近在带新人了解项目&#xff0c;这已经不是第一次带新人&#xff0c;由此引发了我关于新进项目的测试人员如何能够快速介入一个新项目的思考。这里我特指的是项目已经运行一段周期&#xff0c;新进员工或其他项目测试人员中途进入该项目的情况。对于项目一启动&#xff0c;测…

Apache Doris (四十六): Doris数据更新与删除 - 批量删除

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录

分享一个python无人超市管理系统django项目实战源码调试 lw 开题

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人七年开发经验&#xff0c;擅长Java、Python、PHP、.NET、微信小程序、爬虫、大数据等&#xff0c;大家有这一块的问题可以一起交流&#xff01; &#x1f495;&…

文件存储空间管理(空闲表法,空闲链表法,位示图法,成组链表法)

1.存储空间的划分与初始化 1.文件卷&#xff08;逻辑卷)的概念 存储空间的划分:将物理磁盘划分为一个个文件卷&#xff08;逻辑卷、逻辑盘). 2.目录区与文件区 存储空间的初始化&#xff1a;将各个文件卷划分为目录区、文件区。 目录区包含文件目录、空闲表、位示图、超级…

嵌入式养成计划-46----QT--简易版网络聊天室实现--QT如何连接数据库

一百一十九、简易版网络聊天室实现 119.1 QT实现连接TCP协议 119.1.1 基于TCP的通信流程 119.1.2 QT中实现服务器过程 使用QTcpServer实例化一个服务器对象设置监听状态&#xff0c;通过listen()函数&#xff0c;可以监听特定的主机&#xff0c;也可以监听所有客户端&#x…

Unity读取写入Excel

1.在Plugins中放入dll&#xff0c;118开头的dll在Unity安装目录下&#xff08;C:\Program Files\Unity\Editor\Data\Mono\lib\mono\unity&#xff09; 2.写Excel public void WriteExcel(){//文件地址FileInfo newFile new FileInfo(Application.dataPath "/test.xlsx…

v-on 可以监听多个方法吗?

目录 ​编辑 前言&#xff1a;Vue 3 中的 v-on 指令 详解&#xff1a;v-on 指令的基本概念 用法&#xff1a;v-on 指令监听多个方法 解析&#xff1a;v-on 指令的优势和局限性 优势 局限性 **v-on 指令的最佳实践** - **适度监听**&#xff1a; - **方法抽离**&#x…

【并发编程】多线程读写同一变量的并发问题(并发编程启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化&#xff0c;文章内容兼具广度、深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于…

《动手学深度学习 Pytorch版》 10.1 注意力提示

10.1.1 生物学中的注意力提示 “美国心理学之父” 威廉詹姆斯提出的双组件&#xff08;two-component&#xff09;框架&#xff1a; 非自主性提示&#xff1a;基于环境中物体的突出性和易见性 自主性提示&#xff1a;受到了认知和意识的控制 10.1.2 查询、键和值 注意力机制…

一款功能强大的音乐曲谱软件Guitar Pro 8 .1.1for Mac 中文破解版

Guitar Pro 8 .1.1for Mac 中文破解版是一款功能强大的音乐曲谱软件&#xff0c;非常适合学习如何玩&#xff0c;改进技巧&#xff0c;重现喜爱的歌曲或陪伴自己。可以帮助我们进行吉他的学习、绘谱与创作&#xff0c;它包含了几乎所有的吉他现有指法及音色&#xff0c;在做弹拨…