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

本文主要是介绍osg实现物体沿着控制点生成的Cardinal样条轨迹曲线运动,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

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 switchreturn 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_NAMESPACEclass myEventHandler;class osgCardinal : public QWidget
{Q_OBJECTpublic: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.5502auto 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 forreturn 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);

这篇关于osg实现物体沿着控制点生成的Cardinal样条轨迹曲线运动的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/268080

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

AI一键生成 PPT

AI一键生成 PPT 操作步骤 作为一名打工人,是不是经常需要制作各种PPT来分享我的生活和想法。但是,你们知道,有时候灵感来了,时间却不够用了!😩直到我发现了Kimi AI——一个能够自动生成PPT的神奇助手!🌟 什么是Kimi? 一款月之暗面科技有限公司开发的AI办公工具,帮助用户快速生成高质量的演示文稿。 无论你是职场人士、学生还是教师,Kimi都能够为你的办公文

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

poj 1287 Networking(prim or kruscal最小生成树)

题意给你点与点间距离,求最小生成树。 注意点是,两点之间可能有不同的路,输入的时候选择最小的,和之前有道最短路WA的题目类似。 prim代码: #include<stdio.h>const int MaxN = 51;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int P;int prim(){bool vis[MaxN];