• osg实现三次样条Cardinal曲线


    目录

    1. 前言

    2. 预备知识

    3. Qt实现的二维Cardinal曲线

    4. 用osg实现三维Cardinal曲线

     4.1. 工具/ 原料

     4.2. 代码实现


    1. 前言

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

    以上是二维下的Cardinal曲线效果,如何用osg实现 三维的Cardinal曲线呢?即像下面那样:

    即:

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

    2. 预备知识

           关于Hermite曲线、Cardinal曲线的数学理论,参见如下博文:

    3. Qt实现的二维Cardinal曲线

           如下博文为Qt实现的二维Cardinal曲线:

    Qt实现三次样条Cardinal曲线

    4. 用osg实现三维Cardinal曲线

     4.1. 工具/ 原料

    开发环境如下:

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

     4.2. 代码实现

    main.cpp

    1. #include "osgCardinal.h"
    2. #include
    3. int main(int argc, char *argv[])
    4. {
    5. QApplication a(argc, argv);
    6. osgCardinal w;
    7. w.show();
    8. return a.exec();
    9. }

    myEventHandler.cpp

    1. #include "myEventHandler.h"
    2. #include
    3. bool myEventHandler::handle(const osgGA::GUIEventAdapter& ea,
    4. osgGA::GUIActionAdapter& aa,
    5. osg::Object* obj, osg::NodeVisitor* nv)
    6. {
    7. auto pViewer = dynamic_cast(&aa);
    8. auto eventType = ea.getEventType();
    9. switch (eventType)
    10. {
    11. case GUIEventAdapter::PUSH:
    12. {
    13. if(_bPickPoint && (GUIEventAdapter::LEFT_MOUSE_BUTTON == ea.getButton()))
    14. {
    15. osgUtil::LineSegmentIntersector::Intersections intersections;
    16. auto bRet = pViewer->computeIntersections(ea, intersections);
    17. if (!bRet) // 判断是否相交
    18. {
    19. return false;
    20. }
    21. auto iter = intersections.begin(); // 取相交的第1个点
    22. auto interPointCoord = iter->getLocalIntersectPoint();
    23. _pOsgCardinal->drawEllipse(interPointCoord);
    24. }
    25. }
    26. break;
    27. } // end switch
    28. return false;
    29. }
    30. void myEventHandler::setPickPoint(bool bPickPoint)
    31. {
    32. _bPickPoint = bPickPoint;
    33. }

    myEventHandler.h

    1. #ifndef MYEVENTHANDLER_H
    2. #define MYEVENTHANDLER_H
    3. #include
    4. #include
    5. using namespace osgGA;
    6. class myEventHandler:public GUIEventHandler
    7. {
    8. public:
    9. myEventHandler(osgCardinal* p) { _pOsgCardinal = p; }
    10. public:
    11. void setPickPoint(bool bPickPoint);
    12. private:
    13. virtual bool handle(const osgGA::GUIEventAdapter& ea,
    14. osgGA::GUIActionAdapter& aa,
    15. osg::Object* obj, osg::NodeVisitor* nv) override;
    16. private:
    17. bool _bPickPoint{false};
    18. osgCardinal* _pOsgCardinal{nullptr};
    19. };
    20. #endif MYEVENTHANDLER_H

    osgCardinal.h 

    1. #pragma once
    2. #include
    3. #include "ui_osgCardinal.h"
    4. using std::list;
    5. QT_BEGIN_NAMESPACE
    6. namespace Ui { class osgCardinalClass; };
    7. QT_END_NAMESPACE
    8. class myEventHandler;
    9. class osgCardinal : public QWidget
    10. {
    11. Q_OBJECT
    12. public:
    13. osgCardinal(QWidget *parent = nullptr);
    14. ~osgCardinal();
    15. public:
    16. // 画点。以小圆表示
    17. void drawEllipse(const osg::Vec3d& pt);
    18. private:
    19. void addBaseScene();
    20. osg::Geode* createGrid();
    21. void valueChanged(double dfValue);
    22. void startDraw();
    23. void pickPoint();
    24. void clear();
    25. // 计算MC矩阵
    26. void calMcMatrix(double s);
    27. // 压入头部和尾部两个点,用于计算
    28. void pushHeadAndTailPoint();
    29. // 画Cardinal曲线
    30. void drawCardinal();
    31. void drawLines(osg::Vec3Array* pVertArray);
    32. private:
    33. Ui::osgCardinalClass *ui;
    34. myEventHandler*_myEventHandler{nullptr};
    35. bool _startPickPoint{false};
    36. bool _lastPointHasPoped{ false }; // 最后一个点是否被弹出(删除)
    37. bool _hasDrawed{ false }; // 以前是否绘制过Cardinal曲线
    38. double _dfMcMatrix[4][4];
    39. list _lstInterPoint;
    40. osg::Vec3Array*_pVertArray{ nullptr };
    41. osg::Geometry* _pCardinalCurveGemo{ nullptr };
    42. };

    osgCardinal.cpp

    1. #include "osgCardinal.h"
    2. #include"myEventHandler.h"
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. using std::vector;
    10. osgCardinal::osgCardinal(QWidget *parent)
    11. : QWidget(parent)
    12. , ui(new Ui::osgCardinalClass())
    13. {
    14. ui->setupUi(this);
    15. setWindowState(Qt::WindowMaximized);
    16. addBaseScene();
    17. ui->doubleSpinBox->setMinimum(0);
    18. ui->doubleSpinBox->setMaximum(1);
    19. ui->doubleSpinBox->setValue(0.5);
    20. ui->doubleSpinBox->setSingleStep(0.1);
    21. connect(ui->doubleSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &osgCardinal::valueChanged);
    22. connect(ui->startDrawBtn, &QAbstractButton::clicked, this, &osgCardinal::startDraw);
    23. connect(ui->clearBtn, &QAbstractButton::clicked, this, &osgCardinal::clear);
    24. connect(ui->pickPointBtn, &QAbstractButton::clicked, this, &osgCardinal::pickPoint);
    25. calMcMatrix(0.5);
    26. }
    27. osgCardinal::~osgCardinal()
    28. {
    29. delete ui;
    30. }
    31. void osgCardinal::valueChanged(double dfValue)
    32. {
    33. auto s = (1 - dfValue) / 2.0;
    34. // 计算MC矩阵
    35. calMcMatrix(s);
    36. drawCardinal();
    37. }
    38. // 画点。以小圆表示
    39. void osgCardinal::drawEllipse(const osg::Vec3d& pt)
    40. {
    41. if (!_lastPointHasPoped && _hasDrawed && !_lstInterPoint.empty())
    42. {
    43. _lstInterPoint.pop_back();
    44. _lastPointHasPoped = true;
    45. }
    46. _lstInterPoint.emplace_back(pt);
    47. auto pGeometry = new osg::Geometry;
    48. auto pPgo = new osg::PolygonOffset();
    49. pPgo->setFactor(-1.0);
    50. pPgo->setUnits(-1.0);
    51. pGeometry->getOrCreateStateSet()->setAttributeAndModes(pPgo);
    52. auto pVertArray = new osg::Vec3Array;
    53. auto radius = 0.2;
    54. auto twoPi = 2 * 3.1415926;
    55. for (auto iAngle = 0.0; iAngle < twoPi; iAngle += 0.001)
    56. {
    57. auto x = pt.x() + radius * std::cosf(iAngle);
    58. auto y = pt.y() + radius * std::sinf(iAngle);
    59. auto z = pt.z()/* + 0.001*/; // 注意:适当增加点,否则和网格重合了,会导致圆形绘制不正常
    60. pVertArray->push_back(osg::Vec3d(x, y, z));
    61. }
    62. pGeometry->setVertexArray(pVertArray);
    63. auto pColorArray = new osg::Vec4Array;
    64. pColorArray->push_back(osg::Vec4d(1.0, 0.0, 0.0, 1.0));
    65. pGeometry->setColorArray(pColorArray/*, osg::Array::BIND_OVERALL*/);
    66. pGeometry->setColorBinding(osg::Geometry::BIND_OVERALL);
    67. pGeometry->addPrimitiveSet(new osg::DrawArrays(GL_POLYGON, 0, pVertArray->size()));
    68. auto pMatrixTransform = ui->osg_widget->getSceneData()->asGroup()->getChild(0)->asTransform()->asMatrixTransform();
    69. pMatrixTransform->addChild(pGeometry);
    70. }
    71. void osgCardinal::pickPoint()
    72. {
    73. _startPickPoint = !_startPickPoint;
    74. _myEventHandler->setPickPoint(_startPickPoint);
    75. if (_startPickPoint)
    76. {
    77. ui->pickPointBtn->setText(QString::fromLocal8Bit("关闭拾取点"));
    78. }
    79. else
    80. {
    81. ui->pickPointBtn->setText(QString::fromLocal8Bit("拾取点"));
    82. }
    83. }
    84. void osgCardinal::startDraw()
    85. {
    86. if (nullptr != _pCardinalCurveGemo) // 如果以前绘制过Cardinal曲线
    87. {
    88. /* 在上次绘制Cardinal曲线时,通过pushHeadAndTailPoint()
    89. * 压入的头部、尾部用户控制的两个点去掉,以重新压入头部、尾部用户控制的两个点
    90. * ,便于绘制本次曲线
    91. */
    92. if (_lstInterPoint.size() >= 0 )
    93. {
    94. _lstInterPoint.pop_front();
    95. }
    96. }
    97. pushHeadAndTailPoint();
    98. drawCardinal();
    99. _hasDrawed = true;
    100. }
    101. // 压入头部和尾部两个点,用于计算
    102. void osgCardinal::pushHeadAndTailPoint()
    103. {
    104. // 随便构造两个点
    105. auto ptBegin = _lstInterPoint.begin();
    106. auto x = ptBegin->x() + 20;
    107. auto y = ptBegin->y() + 20;
    108. auto z = ptBegin->z();
    109. _lstInterPoint.insert(_lstInterPoint.begin(), osg::Vec3d(x, y, z));
    110. auto ptEnd = _lstInterPoint.back();
    111. x = ptEnd.x() + 20;
    112. y = ptEnd.y() + 20;
    113. z = ptBegin->z();
    114. _lstInterPoint.insert(_lstInterPoint.end(), osg::Vec3d(x, y, z));
    115. }
    116. // 画Cardinal曲线
    117. void osgCardinal::drawCardinal()
    118. {
    119. if (_lstInterPoint.size() < 4)
    120. {
    121. return;
    122. }
    123. if (nullptr == _pVertArray)
    124. {
    125. _pVertArray = new osg::Vec3Array();
    126. }
    127. else
    128. {
    129. _pVertArray->clear();
    130. }
    131. auto iter = _lstInterPoint.begin();
    132. ++iter; // 第1个点(基于0的索引)
    133. _pVertArray->push_back(*iter);
    134. --iter;
    135. auto endIter = _lstInterPoint.end();
    136. int nIndex = 0;
    137. while (true)
    138. {
    139. --endIter;
    140. ++nIndex;
    141. if (3 == nIndex)
    142. {
    143. break;
    144. }
    145. }
    146. for (; iter != endIter; ++iter)
    147. {
    148. auto& p0 = *iter;
    149. auto& p1 = *(++iter);
    150. auto& p2 = *(++iter);
    151. auto& p3 = *(++iter);
    152. --iter;
    153. --iter;
    154. --iter;
    155. vectorvtTempPoint;
    156. vtTempPoint.push_back(p0);
    157. vtTempPoint.push_back(p1);
    158. vtTempPoint.push_back(p2);
    159. vtTempPoint.push_back(p3);
    160. for (auto i = 0; i < 4; ++i)
    161. {
    162. vtTempPoint[i] = p0 * _dfMcMatrix[i][0] + p1 * _dfMcMatrix[i][1] + p2 * _dfMcMatrix[i][2] + p3 * _dfMcMatrix[i][3];
    163. }
    164. float t3, t2, t1, t0;
    165. for (double t = 0.0; t < 1; t += 0.01)
    166. {
    167. t3 = t * t * t; t2 = t * t; t1 = t; t0 = 1;
    168. osg::Vec3d newPoint;
    169. newPoint = vtTempPoint[0] * t3 + vtTempPoint[1] * t2 + vtTempPoint[2] * t1 + vtTempPoint[3] * t0;
    170. _pVertArray->push_back(newPoint);
    171. }
    172. }
    173. drawLines(_pVertArray);
    174. }
    175. void osgCardinal::drawLines(osg::Vec3Array* pVertArray)
    176. {
    177. if (nullptr == _pCardinalCurveGemo)
    178. {
    179. _pCardinalCurveGemo = new osg::Geometry;
    180. auto pLineWidth = new osg::LineWidth(50);
    181. _pCardinalCurveGemo->getOrCreateStateSet()->setAttributeAndModes(pLineWidth);
    182. auto pColorArray = new osg::Vec4Array;
    183. pColorArray->push_back(osg::Vec4d(0.0, 1.0, 0.0, 1.0));
    184. _pCardinalCurveGemo->setColorArray(pColorArray/*, osg::Array::BIND_OVERALL*/);
    185. _pCardinalCurveGemo->setColorBinding(osg::Geometry::BIND_OVERALL);
    186. auto pMatrixTransform = ui->osg_widget->getSceneData()->asGroup()->getChild(0)->asTransform()->asMatrixTransform();
    187. pMatrixTransform->addChild(_pCardinalCurveGemo);
    188. }
    189. // 曲线可能变了,先删除上次的曲线
    190. _pCardinalCurveGemo->removePrimitiveSet(0);
    191. _pCardinalCurveGemo->setVertexArray(pVertArray);
    192. // 再用新点绘制新曲线
    193. _pCardinalCurveGemo->addPrimitiveSet(new osg::DrawArrays(GL_LINE_STRIP, 0, pVertArray->size()));
    194. }
    195. // 计算MC矩阵
    196. void osgCardinal::calMcMatrix(double s)
    197. {
    198. _dfMcMatrix[0][0] = -s, _dfMcMatrix[0][1] = 2 - s, _dfMcMatrix[0][2] = s - 2, _dfMcMatrix[0][3] = s;
    199. _dfMcMatrix[1][0] = 2 * s, _dfMcMatrix[1][1] = s - 3, _dfMcMatrix[1][2] = 3 - 2 * s, _dfMcMatrix[1][3] = -s;
    200. _dfMcMatrix[2][0] = -s, _dfMcMatrix[2][1] = 0, _dfMcMatrix[2][2] = s, _dfMcMatrix[2][3] = 0;
    201. _dfMcMatrix[3][0] = 0, _dfMcMatrix[3][1] = 1, _dfMcMatrix[3][2] = 0, _dfMcMatrix[3][3] = 0;
    202. }
    203. void osgCardinal::clear()
    204. {
    205. _lstInterPoint.clear();
    206. _hasDrawed = false;
    207. _lastPointHasPoped = false;
    208. }
    209. osg::Geode* osgCardinal::createGrid()
    210. {
    211. auto pGeode = new osg::Geode;
    212. auto pVertArray = new osg::Vec3Array;
    213. for (auto y = -10; y < 10; ++y)
    214. {
    215. for (auto x = -10; x < 10; ++x)
    216. {
    217. pVertArray->push_back(osg::Vec3d(x, y, 0.0));
    218. pVertArray->push_back(osg::Vec3d(x + 1, y, 0.0));
    219. pVertArray->push_back(osg::Vec3d(x + 1, y + 1, 0.0));
    220. pVertArray->push_back(osg::Vec3d(x, y + 1, 0.0));
    221. }
    222. }
    223. auto iSize = pVertArray->size();
    224. osg::DrawElementsUShort* pEle{ nullptr };
    225. osg::Geometry* pGeomerty{ nullptr };
    226. osg::Vec4Array* pColorArray{ nullptr };
    227. auto nQuardIndex = 0;
    228. bool bNewLineQuard = true; // 新的一行四边形
    229. for (auto iVertIndex = 0; iVertIndex < iSize; ++iVertIndex)
    230. {
    231. if (0 == (iVertIndex % 4))
    232. {
    233. pEle = new osg::DrawElementsUShort(GL_QUADS);
    234. pGeomerty = new osg::Geometry;
    235. pGeomerty->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
    236. pGeomerty->addPrimitiveSet(pEle);
    237. pGeode->addDrawable(pGeomerty);
    238. pGeomerty->setVertexArray(pVertArray);
    239. pColorArray = new osg::Vec4Array();
    240. if (bNewLineQuard)
    241. {
    242. pColorArray->push_back(osg::Vec4d(1.0, 1.0, 1.0, 1.0));
    243. }
    244. else
    245. {
    246. pColorArray->push_back(osg::Vec4d(0.0, 0.0, 0.0, 1.0));
    247. }
    248. ++nQuardIndex;
    249. if (0 != (nQuardIndex % 20))
    250. {
    251. bNewLineQuard = !bNewLineQuard;
    252. }
    253. pGeomerty->setColorArray(pColorArray, osg::Array::Binding::BIND_PER_PRIMITIVE_SET);
    254. }
    255. pEle->push_back(iVertIndex);
    256. } // end for
    257. return pGeode;
    258. }
    259. void osgCardinal::addBaseScene()
    260. {
    261. auto pAxis = osgDB::readRefNodeFile(R"(E:\osg\OpenSceneGraph-Data\axes.osgt)");
    262. if (nullptr == pAxis)
    263. {
    264. OSG_WARN << "axes node is nullpr!";
    265. return;
    266. }
    267. auto pRoot = new osg::Group();
    268. pRoot->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
    269. auto pMatrixRoot = new osg::MatrixTransform;
    270. auto pGrid = createGrid();
    271. pMatrixRoot->addChild(pGrid);
    272. pMatrixRoot->addChild(pAxis);
    273. pRoot->addChild(pMatrixRoot);
    274. pMatrixRoot->setMatrix(osg::Matrix::rotate(osg::inDegrees(60.0), osg::Vec3(1, 0, 0)));
    275. ui->osg_widget->setSceneData(pRoot);
    276. // 操控器一定要加上,否则场景不会显示
    277. ui->osg_widget->setCameraManipulator(new osgGA::TrackballManipulator);
    278. ui->osg_widget->addEventHandler(new osgViewer::WindowSizeHandler);
    279. ui->osg_widget->addEventHandler(new osgViewer::StatsHandler);
    280. _myEventHandler = new myEventHandler(this);
    281. ui->osg_widget->addEventHandler(_myEventHandler);
    282. // 模拟鼠标滚轮朝向人滚动三次,以便场景离人显得更近些
    283. for (auto iLoop = 0; iLoop < 3; ++iLoop)
    284. {
    285. ui->osg_widget->getEventQueue()->mouseScroll(osgGA::GUIEventAdapter::SCROLL_DOWN);
    286. }
    287. }

    说明:

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

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

          ui->osg_widget是QtOsgView类型指针,QtOsgView.h、QtOsgView.cpp文件参见:osg嵌入到Qt窗体,实现Qt和osg混合编程 博文。

  • 相关阅读:
    [架构之路-229]:计算机体硬件与系结构 - 计算机系统的矩阵知识体系结构
    Ajax入门及jQuery库对Ajax的封装
    WEB安全-SQL注入
    高通SDX12:SFE(shortcut-fe)软加速驱动效果调测
    自用版:客服话术大全
    高精度定时器学习(通过官方手册学习)
    Java - 单例模式详解
    c++ | makefile | 编译 | 链接库
    Android Datastore 动态创建与源码解析
    logging java日志选择
  • 原文地址:https://blog.csdn.net/danshiming/article/details/133925477