² 貝塞爾曲線
貝塞爾曲線是通過一組多邊折線的各頂點來定義。在各頂點中,曲線經過第一點和最后一點,其余各點則定義曲線的導數、階次和形狀。第一條和最后一條則表示曲線起點和終點的切線方向。
² B樣條曲線
針對貝塞爾曲線存在的一些缺點,數學家們提出了B樣條方法,在保留貝塞爾全部優點的同時,克服可貝塞爾方法的弱點。
1) 二次B樣條曲線
2) 三次B樣條曲線
QT中的QPainter提供了繪制貝塞爾曲線的相關API:
void QPainterPath::quadTo(const QPointF &c, const QPointF &endPoint)
void QPainterPath::cubicTo(const QPointF &c1, const QPointF &c2, const QPointF &endPoint)
void QPainter::drawPath(const QPainterPath &path)
Widget.h
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
///////////////////////////////////////////////////////////// /// @file Widget.h /// @brief 繪制B樣條曲線Widget類 /// /// 通過鼠標點擊來繪制B樣條曲線 /// @author Michael Joessy /// @date 2019-07-02 ///////////////////////////////////////////////////////////// #ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QVector> class Widget : public QWidget { Q_OBJECT public : Widget(QWidget *parent = nullptr); ~Widget(); protected : virtual void mousePressEvent(QMouseEvent *event); virtual void mouseReleaseEvent(QMouseEvent *event); virtual void mouseDoubleClickEvent(QMouseEvent *event); virtual void mouseMoveEvent(QMouseEvent *event); virtual void paintEvent(QPaintEvent *event); private : void drawSpline(); qreal N( int k, int i, qreal u); qreal N1( int i, qreal u); qreal N2( int i, qreal u); qreal N3( int i, qreal u); private : QVector<QPointF> m_ctrlPoints; // 控制點 QVector<QPointF> m_curvePoints; // 曲線上的點 }; #endif // WIDGET_H |
Widget.pp
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
#include
"Widget.h"
#include <QMouseEvent> #include <QPainter> #include <cmath> Widget::Widget(QWidget *parent) : QWidget(parent) { } Widget::~Widget() { } void Widget::mousePressEvent(QMouseEvent *event) { // 單擊鼠標左鍵獲取控制點 if (event->buttons() == Qt::LeftButton){ m_ctrlPoints.push_back(event->pos()); } // 單擊鼠標右鍵清空控制點 else if (event->buttons() == Qt::RightButton) { m_ctrlPoints.clear(); } update(); } void Widget::mouseReleaseEvent(QMouseEvent *event) { } void Widget::mouseDoubleClickEvent(QMouseEvent *event) { } void Widget::mouseMoveEvent(QMouseEvent *event) { } void Widget::paintEvent(QPaintEvent *event) { drawSpline(); } void Widget::drawSpline() { QPainter painter( this ); int currentK = 3 ; // 階數 m_curvePoints.clear(); for (qreal u = currentK; u < m_ctrlPoints.size(); u += 0 . 01 ){ QPointF pt( 0 . 0 , 0 . 0 ); for ( int i = 0 ; i < m_ctrlPoints.size(); ++i){ QPointF pts = m_ctrlPoints[i]; pts *= N(currentK, i, u); pt += pts; } m_curvePoints.push_back(pt); } // draw control points QPen ctrlPen1(QColor( 0 , 0 , 255 )); ctrlPen1.setWidth( 5 ); painter.setPen(ctrlPen1); for ( int i = 0 ; i < m_ctrlPoints.size(); ++i){ painter.drawPoint(m_ctrlPoints[i]); } // draw control lines QPen ctrlPen2(QColor( 255 , 0 , 0 )); ctrlPen2.setWidth( 1 ); ctrlPen2.setStyle(Qt::DashDotDotLine); painter.setPen(ctrlPen2); for ( int i = 0 ; i < m_ctrlPoints.size() - 1 ; ++i){ painter.drawLine(m_ctrlPoints[i], m_ctrlPoints[i + 1 ]); } // draw spline curve QPen curvePen(QColor( 0 , 0 , 0 )); curvePen.setWidth( 2 ); painter.setPen(curvePen); for ( int i = 0 ; i < m_curvePoints.size() - 1 ; ++i){ painter.drawLine(m_curvePoints[i], m_curvePoints[i + 1 ]); } } qreal Widget::N( int k, int i, qreal u) { switch (k) { case 1 : return N1(i, u); case 2 : return N2(i, u); case 3 : return N3(i, u); default : break ; } } qreal Widget::N1( int i, qreal u) { qreal t = u - i; if ( 0 <= t && t < 1 ){ return t; } if ( 1 <= t && t < 2 ){ return 2 - t; } return 0 ; } qreal Widget::N2( int i, qreal u) { qreal t = u - i; if ( 0 <= t && t < 1 ){ return 0 . 5 * t * t; } if ( 1 <= t && t < 2 ){ return 3 * t - t * t - 1 . 5 ; } if ( 2 <= t && t < 3 ){ return 0 . 5 * pow( 3 - t, 2 ); } return 0 ; } qreal Widget::N3( int i, qreal u) { qreal t = u - i; qreal a = 1 . 0 / 6 . 0 ; if ( 0 <= t && t < 1 ){ return a * t * t * t; } if ( 1 <= t && t < 2 ){ return a * (- 3 * pow(t - 1 , 3 ) + 3 * pow(t - 1 , 2 ) + 3 * (t - 1 ) + 1 ); } if ( 2 <= t && t < 3 ){ return a * ( 3 * pow(t - 2 , 3 ) - 6 * pow(t - 2 , 2 ) + 4 ); } if ( 3 <= t && t < 4 ){ return a * pow( 4 - t, 3 ); } return 0 ; } |
上述算法有點簡單,https://github.com/vkorchagin/animated-b-spline提供了比較好的算法例子,值得參考。
給定B樣條曲線的控制點。 這些點在屏幕上移動,從而使樣條動畫。
B樣條通過de Boor算法轉換為合成Bezier曲線。 貝塞爾曲線用de Casteljau算法進行插值。
特征:
添加和刪除控制點。
通過de Casteljau算法更改插值質量。
切換抗鋸齒。
不斷變化的動畫速度。
切換可見點和線。