QT繪制B樣條曲線


²  貝塞爾曲線

貝塞爾曲線是通過一組多邊折線的各頂點來定義。在各頂點中,曲線經過第一點和最后一點,其余各點則定義曲線的導數、階次和形狀。第一條和最后一條則表示曲線起點和終點的切線方向。

²  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算法更改插值質量。
切換抗鋸齒。
不斷變化的動畫速度。
切換可見點和線。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM