• Qt实现三次样条Cardinal曲线


    目录

    1. 前言

    2. 预备知识

    3. 代码实现

    4. 附录


    1. 前言

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

    2. 预备知识

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

    3. 代码实现

    如下为用Qt实现的Cardinal曲线

    Cardinal.h

    1. #pragma once
    2. #include
    3. #include "ui_Cardinal.h"
    4. QT_BEGIN_NAMESPACE
    5. namespace Ui { class CardinalClass; };
    6. QT_END_NAMESPACE
    7. class Cardinal : public QWidget
    8. {
    9. Q_OBJECT
    10. public:
    11. Cardinal(QWidget *parent = nullptr);
    12. ~Cardinal();
    13. private:
    14. Ui::CardinalClass *ui;
    15. };

    Cardinal.cpp 

    1. #include "Cardinal.h"
    2. Cardinal::Cardinal(QWidget *parent)
    3. : QWidget(parent)
    4. , ui(new Ui::CardinalClass())
    5. {
    6. ui->setupUi(this);
    7. setWindowState(Qt::WindowMaximized);
    8. ui->doubleSpinBox->setMinimum(0);
    9. ui->doubleSpinBox->setMaximum(1);
    10. ui->doubleSpinBox->setValue(0.5);
    11. ui->doubleSpinBox->setSingleStep(0.1);
    12. connect(ui->doubleSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged), ui->myCardinalPanel, &CardinalPanel::valueChanged);
    13. connect(ui->startDrawBtn, &QAbstractButton::clicked, ui->myCardinalPanel, &CardinalPanel::startDraw);
    14. connect(ui->clearBtn, &QAbstractButton::clicked, ui->myCardinalPanel, &CardinalPanel::clear);
    15. }
    16. Cardinal::~Cardinal()
    17. {
    18. delete ui;
    19. }

    CardinalPanel.h 

    1. #pragma once
    2. #include
    3. #include "ui_CardinalPanel.h"
    4. #include
    5. using std::list;
    6. class CardinalPanel : public QWidget
    7. {
    8. Q_OBJECT
    9. public:
    10. CardinalPanel(QWidget *parent = nullptr);
    11. ~CardinalPanel();
    12. public:
    13. void valueChanged(double value);
    14. void startDraw();
    15. void clear();
    16. private:
    17. virtual void mousePressEvent(QMouseEvent* event) override;
    18. virtual void paintEvent(QPaintEvent* event) override;
    19. private:
    20. // 画鼠标左键按下选中的点
    21. void drawPoint();
    22. // 画Cardinal曲线
    23. void drawCardinal();
    24. // 计算MC矩阵
    25. void calMcMatrix(double s);
    26. // 压入头部和尾部两个点,用于计算
    27. void pushHeadAndTailPoint();
    28. private:
    29. Ui::CardinalPanelClass ui;
    30. bool m_bStartDraw{false};
    31. double m_dfMcMatrix[4][4];
    32. list m_lstPoint;
    33. QPainterPath path;
    34. };

    CardinalPanel.cpp

    1. #include "CardinalPanel.h"
    2. #include
    3. #include
    4. #include
    5. #include
    6. using std::vector;
    7. CardinalPanel::CardinalPanel(QWidget* parent)
    8. : QWidget(parent)
    9. {
    10. ui.setupUi(this);
    11. valueChanged(0.5);
    12. }
    13. CardinalPanel::~CardinalPanel()
    14. {}
    15. void CardinalPanel::valueChanged(double value)
    16. {
    17. auto s = (1 - value) / 2.0;
    18. // 计算MC矩阵
    19. calMcMatrix(s);
    20. update();
    21. }
    22. // 计算MC矩阵
    23. void CardinalPanel::calMcMatrix(double s)
    24. {
    25. m_dfMcMatrix[0][0] = -s, m_dfMcMatrix[0][1] = 2 - s, m_dfMcMatrix[0][2] = s - 2, m_dfMcMatrix[0][3] = s;//Mc矩阵
    26. m_dfMcMatrix[1][0] = 2 * s, m_dfMcMatrix[1][1] = s - 3, m_dfMcMatrix[1][2] = 3 - 2 * s, m_dfMcMatrix[1][3] = -s;
    27. m_dfMcMatrix[2][0] = -s, m_dfMcMatrix[2][1] = 0, m_dfMcMatrix[2][2] = s, m_dfMcMatrix[2][3] = 0;
    28. m_dfMcMatrix[3][0] = 0, m_dfMcMatrix[3][1] = 1, m_dfMcMatrix[3][2] = 0, m_dfMcMatrix[3][3] = 0;
    29. }
    30. void CardinalPanel::clear()
    31. {
    32. m_bStartDraw = false;
    33. m_lstPoint.clear();
    34. update();
    35. }
    36. // 压入头部和尾部两个点,用于计算
    37. void CardinalPanel::pushHeadAndTailPoint()
    38. {
    39. // 随便构造两个点
    40. auto ptBegin = m_lstPoint.begin();
    41. auto x = ptBegin->x() + 20;
    42. auto y = ptBegin->y() + 20;
    43. m_lstPoint.insert(m_lstPoint.begin(), QPoint(x, y));
    44. auto ptEnd = m_lstPoint.back();
    45. x = ptEnd.x() + 20;
    46. y = ptEnd.y() + 20;
    47. m_lstPoint.insert(m_lstPoint.end(), QPoint(x, y));
    48. }
    49. void CardinalPanel::startDraw()
    50. {
    51. m_bStartDraw = true;
    52. pushHeadAndTailPoint();
    53. update();
    54. }
    55. void CardinalPanel::mousePressEvent(QMouseEvent* event)
    56. {
    57. if ((Qt::LeftButton != event->button()))
    58. {
    59. return QWidget::mousePressEvent(event);
    60. }
    61. m_lstPoint.insert(m_lstPoint.end(), event->pos());
    62. update();
    63. QWidget::mousePressEvent(event);
    64. }
    65. // 画鼠标左键按下选中的点
    66. void CardinalPanel::drawPoint()
    67. {
    68. QPainter painter(this);
    69. painter.setBrush(QColor(Qt::red));
    70. const auto iPointSize = 8;
    71. // 先画鼠标左键按下选中的点
    72. auto nPointIndex = 0;
    73. for (auto iter = m_lstPoint.begin(); iter != m_lstPoint.end(); ++iter)
    74. {
    75. // 头部、尾部的两个控制点不绘制
    76. if (m_bStartDraw && ( (iter == m_lstPoint.begin()) || (*iter == m_lstPoint.back()) ))
    77. {
    78. continue;
    79. }
    80. painter.drawEllipse(*iter, iPointSize, iPointSize);
    81. }
    82. }
    83. // 画Cardinal曲线
    84. void CardinalPanel::drawCardinal()
    85. {
    86. if (m_lstPoint.size() < 4)
    87. {
    88. return;
    89. }
    90. QPainter painter(this);
    91. QPen pen(QColor(Qt::green), 6);
    92. painter.setPen(pen);
    93. path.clear();
    94. auto iter = m_lstPoint.begin();
    95. ++iter; // 第1个点(基于0的索引)
    96. path.moveTo(*iter);
    97. --iter;
    98. auto endIter = m_lstPoint.end();
    99. int nIndex = 0;
    100. while (true)
    101. {
    102. --endIter;
    103. ++nIndex;
    104. if (3 == nIndex)
    105. {
    106. break;
    107. }
    108. }
    109. for (; iter != endIter; ++iter)
    110. {
    111. auto& p0 = *iter;
    112. auto& p1 = *(++iter);
    113. auto& p2 = *(++iter);
    114. auto& p3 = *(++iter);
    115. --iter;
    116. --iter;
    117. --iter;
    118. vectorvtTempPoint;
    119. vtTempPoint.push_back(p0);
    120. vtTempPoint.push_back(p1);
    121. vtTempPoint.push_back(p2);
    122. vtTempPoint.push_back(p3);
    123. //double value[4][1];
    124. for (auto i = 0; i < 4; ++i)
    125. {
    126. vtTempPoint[i] = m_dfMcMatrix[i][0] * p0 + m_dfMcMatrix[i][1] * p1 + m_dfMcMatrix[i][2] * p2 + m_dfMcMatrix[i][3] * p3;
    127. }
    128. double t3, t2, t1, t0;
    129. for (double t = 0.0; t < 1; t += 0.01)
    130. {
    131. t3 = t * t * t; t2 = t * t; t1 = t; t0 = 1;
    132. auto newPoint = t3 * vtTempPoint[0] + t2 * vtTempPoint[1] + t1 * vtTempPoint[2] + t0 * vtTempPoint[3];
    133. path.lineTo(newPoint);
    134. }
    135. }
    136. painter.drawPath(path);
    137. }
    138. void CardinalPanel::paintEvent(QPaintEvent* event)
    139. {
    140. drawPoint();
    141. // 再画Cardinal曲线
    142. if (m_bStartDraw)
    143. {
    144. drawCardinal();
    145. }
    146. }

    运行效果如下:


     

    可以看到当u值越大时,曲线越尖锐,当变为1时,就成了直线;越小越光滑。

    4. 附录

     如果想实现3D版的Cardinal曲线,请参考:osg实现三次样条Cardinal曲线

  • 相关阅读:
    MySQL主从复制与读写分离
    JVM基础:字节码文件详解①
    事件介绍、两种事件注册的介绍、onload事件、onclick事件、onblur事件、onchange事件、onsubmit事件
    Oracle存储过程入门教程(通俗理解存储过程)
    酷开科技丨酷开系统9.2:引领大屏智能化新纪元
    《ABP Framework 极速开发》教程首发
    面试 Python 基础八股文十问十答第七期
    【刷题训练】牛客:JZ31 栈的压入、弹出序列
    C语言for循环必备练习题
    SHT31/85温湿度传感器驱动代码(基于GD32F103)
  • 原文地址:https://blog.csdn.net/danshiming/article/details/133854581