2014-12-02

[OF]畫畫看吧!長頸鹿!

老實說  比起寫Kinect的學習筆記
還是做些純粹與長頸鹿有關的事物比較合我喜好
所以有想看Kinect V2下一篇的...再等等吧(微笑

這篇起緣於我的同事常常用程式去畫一些有趣的東西(妖怪吃小朋友之類的
因為覺得有趣,就試著也來玩玩看純粹用程式做最基本的繪圖
也就是從單純的幾何圖形與曲線去構成圖案
我的一開始的作品並沒有畫長頸鹿...
在貼出來後就不只一個人問我「長頸鹿勒?」
所以...來看Demo吧!


舌頭是重點!!!
其實這個Demo並沒有完全發揮用程式畫圖所帶來的優勢
包含像是各部位的形變與到變色這些才是程式的強項
But...這個Demo我覺得這樣比較可愛所以就這樣!!

=========分隔線========

整個長頸鹿只用到了三種東西去完成:
Circle、Line、Bezier Curve

相信前兩者應該不用我多做介紹
在OF中就只要在draw下使用ofCircle以及ofLine這兩個function就足夠

Bezier Curve(貝茲曲線)相信有使用繪圖軟體或對電腦圖學稍微有涉略的人應該都有聽過
最初是由Paul de Casteljau在1959年根據de Casteljau演算法所計算出的結果
1962時法國工程師Pierre Bézier在汽車設計方面發揚光大 (from wiki)
竟然是數值分析領域的領域,那肯定就有滿滿的數學式
看到這裡不用擔心,筆者完全沒有打算在此去探討原理
要使用Bezier Curve,最直覺的方式就是看例子
(以下感謝wiki提供的完美例子)

Quadratic Bezier Curve(二次貝茲曲線):

二次貝茲曲線有一個控制點
P0是出發點、P2是終點、P1是控制點
假設在t=0.5的情況
首先取得P0→P1的中點A與P1→P2中點B
再取得A→B的中點,就是這個位置的點
只要t從0~1就能得到Bezier Curve

Cubic Bezier Curve(三次貝茲曲線):

同理,三次貝茲曲線有兩個控制點
※想看更高次方的範例的就請直接參考wiki

無論是OF還是Processing
一般來說都是提供二次與三次的Bezier Curve
因此若要畫出更複雜的曲線
可以用多個Bezier Curve來兜出想要的結果

=====================================

比較底層的部分解釋完畢後
再來就要告訴大家怎麼畫長頸鹿了!!
由於這次是比較屬於個人各憑想像力去發揮
所以接下來的做法完全可以根據個人喜好去改變喔

首先是長頸鹿成品:

筆者根據畫的順序將長頸鹿分成四個部分

  1. 輪廓
  2. 眼睛
  3. 舌頭
  4. 斑紋

以下分別針對這四個部分進行說明

=輪廓=

整個輪廓是由三條直線配上三個Bezier Curve組成
紅色框是嘴巴,綠色框是角,橘色是耳朵
以下提供用到的幾個主要點的大概位置:

筆者這裡是使用OF提供的ofPath去做繪製
程式碼的部份就留在後面一起介紹

=眼睛=
沒甚麼特別的就是一個ofCircle...

=舌頭=

由一組Bezier Curve所繪製
比較有趣的部份就在於開頭跟結尾的位置要怎麼得到
由於舌頭是連在嘴巴的Beizer Curve上
因此這兩點就得從此得來
OF提供的ofBezierPoint(),可以得到某個Bezier Curve上的特定點
控制點的部分可以參考上圖

=斑紋=

兩個斑紋各由一組Bezier Curve所繪製
找開頭跟結尾的問題在這裡同樣要處裡
這次是連在脖子的直線上,只要透過getInterpolate()的方式就可以得到直線上的點
詳細作法請參考程式碼的部分

==========================

以下是繪製長頸鹿的部分程式碼
首先是setup()的部分:

1:  void setupGiraffe(float fScale = 200)  
2:  {  
3:       _fScale = fScale;  
4:         
5:       _Outline.clear();  
6:       _Outline.setFilled(false);  
7:       _Outline.setStrokeWidth(3.0);  
8:       _Outline.setStrokeColor(ofColor(255));  
9:    
10:       _DrawStartPos.set(-0.15 * fScale, -(fScale * 0.95));  
11:       ofPoint StartPos_ = _DrawStartPos;  
12:    
13:       _Outline.moveTo(StartPos_);  
14:                   
15:       //Mouse  
16:       _Outline.bezierTo( StartPos_.x - (0.4 * fScale), StartPos_.y,   
17:                          StartPos_.x - (0.4 * fScale), StartPos_.y - (0.1 * fScale),  
18:                          StartPos_.x, StartPos_.y - (0.2 * fScale));  
19:    
20:    
21:       //Tongue  
22:       _TongueStart = ofBezierPoint( StartPos_,  
23:                                     ofPoint(StartPos_.x - (0.4 * fScale), StartPos_.y),  
24:                                     ofPoint(StartPos_.x - (0.4 * fScale), StartPos_.y - (0.1 * fScale)),  
25:                                     ofPoint(StartPos_.x, StartPos_.y - (0.2 * fScale)),  
26:                                     0.2  
27:                                    );  
28:    
29:       _TongueEnd = ofBezierPoint( StartPos_,  
30:                                   ofPoint(StartPos_.x - (0.4 * fScale), StartPos_.y),  
31:                                   ofPoint(StartPos_.x - (0.4 * fScale), StartPos_.y - (0.1 * fScale)),  
32:                                   ofPoint(StartPos_.x, StartPos_.y - (0.2 * fScale)),  
33:                                   0.3  
34:                                  );       
35:    
36:       //Horn  
37:       StartPos_.set(StartPos_.x, StartPos_.y - (0.2 * fScale));  
38:       _Outline.bezierTo( StartPos_.x + (0.05 * fScale), StartPos_.y - (0.15 * fScale),  
39:                          StartPos_.x + (0.125 * fScale), StartPos_.y - (0.175 * fScale),  
40:                          StartPos_.x + (0.125 * fScale), StartPos_.y);  
41:    
42:       //Ear  
43:       StartPos_.set(StartPos_.x + (0.125 * fScale), StartPos_.y);  
44:       _Outline.bezierTo( StartPos_.x + (0.15 * fScale), StartPos_.y,  
45:                          StartPos_.x + (0.15 * fScale), StartPos_.y + (0.15 * fScale),  
46:                          StartPos_.x, StartPos_.y + (0.15 * fScale));  
47:    
48:       //Neck  
49:       _Outline.lineTo( StartPos_.x + (0.175 * fScale), StartPos_.y + fScale);  
50:       _Outline.lineTo( StartPos_.x - (0.125 * fScale), StartPos_.y + fScale);  
51:       _Outline.close();  
52:    
53:       //Spot  
54:       ofVec2f LinePos_;  
55:       LinePos_.set(StartPos_.x, StartPos_.y + (0.15 * fScale));  
56:       _SpotsStart1 = LinePos_.getInterpolated(ofVec2f(StartPos_.x + (0.175 * fScale), StartPos_.y + fScale), 0.2);  
57:       _SpotsEnd1 = LinePos_.getInterpolated(ofVec2f(StartPos_.x + (0.175 * fScale), StartPos_.y + fScale), 0.5);  
58:         
59:       LinePos_ = _DrawStartPos;  
60:       _SpotsStart2 = LinePos_.getInterpolated(ofVec2f(StartPos_.x - (0.125 * fScale), StartPos_.y + fScale), 0.6);  
61:       _SpotsEnd2 = LinePos_.getInterpolated(ofVec2f(StartPos_.x - (0.125 * fScale), StartPos_.y + fScale), 0.9);  
62:  }  

3:紀錄比例
為了達到之後對長頸鹿放大縮小時樣子比例不會跑掉
這裡以脖子的長度作為控制整體比例的數值
也就是說,之後每個控制點都會是根據這個值去算實際的位置
當然,在第一次畫的時候可以開心地直接給數字就好
之後再調整即可

5:8:設定輪廓的參數
ofPath的顏色與填滿與否似乎不受常用的ofSetColor()ofSetFill()所控制
因此得直接針對物件本身設定
※特別注意,ofPath預設的Stroke Width = 0喔!

10:13:設置輪廓起始點
筆者將嘴巴下緣作為繪畫的起始點,為了之後繪製上的方便
而整個座標的中心點則是在脖子下方的線的中間

21:34:取得舌頭的起點與終點
承如剛剛說明所提到,舌頭的起點與終點是在嘴巴的Bezier Curve上
因此這裡就透過ofBezierPoint去取得比例在0.2與0.3的點作為起點與終點

49:51:繪製脖子並封閉輪廓
可以看到雖然脖子一共由三條線段組成,但這裡卻只有兩個lineTo
原因在於ofPath最後可以呼叫close()將最後到達的點與起點相連
這點跟許多繪圖軟體的作法是類似的

53:61:取得斑紋的起點與終點
要取得脖子線上的點,不像Bezier Curve這麼直覺地用一個function就解決
假設一條直線的兩端點為S, E
OF提供了以下的用法來取得此直線上特定比例t的點:
Result = S.getInterpolated(E, t);

========================================

再來是draw()的部份:
1:  void drawGiraffe()  
2:  {  
3:       ofSetColor(255);  
4:    
5:       _Outline.draw();  
6:    
7:       //Tongue  
8:       ofBezier( _TongueStart.x, _TongueStart.y,  
9:                 _TongueStart.x - (0.075 * _fScale), _TongueStart.y + (0.05 * _fScale),  
10:                _TongueEnd.x - (0.075 * _fScale), _TongueEnd.y + (0.05 * _fScale),  
11:                _TongueEnd.x, _TongueEnd.y  
12:               );  
13:    
14:       //Eyes  
15:       ofCircle(_DrawStartPos.x, _DrawStartPos.y - (0.1 * _fScale), (0.025 * _fScale));  
16:    
17:       //Spots  
18:       ofBezier( _SpotsStart1.x, _SpotsStart1.y,  
19:                 _SpotsStart1.x - (0.1 * _fScale), _SpotsStart1.y,  
20:                 _SpotsEnd1.x - (0.1 * _fScale), _SpotsEnd1.y,  
21:                 _SpotsEnd1.x, _SpotsEnd1.y  
22:                );  
23:    
24:       ofBezier( _SpotsStart2.x, _SpotsStart2.y,  
25:                 _SpotsStart2.x + (0.1 * _fScale), _SpotsStart2.y,  
26:                 _SpotsEnd2.x + (0.1 * _fScale), _SpotsEnd2.y,  
27:                 _SpotsEnd2.x, _SpotsEnd2.y  
28:                );  
29:  }  

5:繪製輪廓

7:28:繪製舌頭、眼睛、斑紋
這裡筆者並沒有全部都用ofPath存下整個Bezier Curve
因此直接使用ofBezier來作繪製

=========分隔線========

本篇的簡單介紹了Bezier Curve,並說明如何運用它畫出自己作品
關於Openframeworks所提供的繪圖功能,其實還有很多有趣的地方可以寫
但這篇的對象畢竟不是針對完全OF新手的角度
如果對上面的程式碼有任何疑問都歡迎留言提問

至於有興趣想了解開頭的旋轉長頸鹿的
主要的程式碼就在下方,歡迎參考喔

========================================

旋轉吧!長頸鹿!

1:  void ofApp::setup()  
2:  {  
3:       ofBackground(85, 143, 216);  
4:       ofSetVerticalSync(true);  
5:         
6:       _Giraffe.setupGiraffe(200);  
7:       _Giraffe2.setupGiraffe(200);  
8:       _fRotateVel = 60.0;  
9:       _fTimer = ofGetElapsedTimef();  
10:       _fNowDegree = 0.0;  
11:       _iNumGiraffe = 1;  
12:    
13:       _AnimGiraffeSize.setCurve(AnimCurve::EASE_IN_EASE_OUT);  
14:       _AnimGiraffeSize.setDuration(2.0);  
15:       _AnimGiraffeSize.setRepeatType(AnimRepeat::LOOP_BACK_AND_FORTH);  
16:         
17:  }  
18:    
19:  //--------------------------------------------------------------  
20:  void ofApp::update()  
21:  {  
22:       float fDelta_ = ofGetElapsedTimef() - _fTimer;  
23:       _fTimer += fDelta_;  
24:    
25:       _fNowDegree += fDelta_ * _fRotateVel;  
26:       if(_fNowDegree >= 360.0)  
27:       {  
28:            _fNowDegree -= 360.0;  
29:       }  
30:    
31:       _AnimGiraffeSize.update(fDelta_);  
32:       if(_AnimGiraffeSize.isAnimating())  
33:       {  
34:            _Giraffe.setupGiraffe(_AnimGiraffeSize.getCurrentValue());  
35:            _Giraffe2.setupGiraffe((200 - _AnimGiraffeSize.getCurrentValue()) + 100);  
36:       }  
37:  }  
38:    
39:  //--------------------------------------------------------------  
40:  void ofApp::draw()  
41:  {  
42:       ofSetColor(255);  
43:         
44:       float fDiffDegree_ = 360.0/_iNumGiraffe;  
45:         
46:       ofPushMatrix();  
47:       {  
48:            ofTranslate(ofGetWindowWidth()/2, ofGetWindowHeight()/2);  
49:              
50:            for(int idx_ = 0; idx_ < _iNumGiraffe; ++idx_)  
51:            {  
52:                 ofPushMatrix();  
53:                 {  
54:                      ofRotateZ(-(_fNowDegree + idx_ * fDiffDegree_));  
55:                        
56:                      if(idx_ % 2 == 0)  
57:                      {  
58:                           _Giraffe.drawGiraffe();  
59:                      }  
60:                      else  
61:                      {  
62:                           _Giraffe2.drawGiraffe();  
63:                      }  
64:                 }  
65:                 ofPopMatrix();  
66:            }  
67:              
68:       }  
69:       ofPopMatrix();  
70:  }  

沒有留言:

張貼留言