1. 程式人生 > >專案開發中的貝塞爾曲線

專案開發中的貝塞爾曲線

本文由鄒啟文授權網易雲社群釋出。


郵箱大師PC版中,設計師提出了一個很妙的想法: 發信時,出現一個飛機,從寫信中央飛往進度目的地。 


附加要求: 
1,飛行曲線,飛機先加速,然後減速抵達終點 
2,飛行途中,需要轉換飛機朝向 
3,飛行途中,飛機漸漸變小 
體驗:網易郵箱大師電腦版 


實現方法:

 
1. 飛行曲線 


我們選擇了二次貝塞爾曲線,原因是簡單,可計算。 
數學公式:B(t) = (1-t)^2 * P0 + 2 * t * (1-t) * P1 + t^2 *P2, t=[0,1] 
圖片描述 
(圖片來源於網路,此處是為了講解清楚) 


已知起飛點P0,終點P2,起飛角度(或斜率,切線P0P1),降落角度(或斜率,切線P1P2),求P1 
將起飛角度轉換(k=tan(θ))成斜率k1,降落角度轉成斜率k2,根據斜率公式y=k*x+b可得 
P0.y = k1 * P0.x + b1; 
P1.y = k1 * P1.x + b1; 
P2.y = k2 * P2.x + b2; 
P1.y = k2 * P1.x + b2; 
至此,便可求得P1座標。(可調整角度(或k1,k2)以滿足實際要求) 


曲線雖然推匯出來,但是,飛機先加速然後減速,如何實現呢? 


注意觀察,公式中t的取值範圍是0~1,我們可以讓飛機在前面小部分時間走過大部分距離,後面 
大部分時間走過小部分距離來做到。 
為了靈活調整,以及總時間固定的情況下,我們選擇了一種簡單的方法,將時間分片,人為的構造 
出一段前面加速後面減速的時間曲線。 


示例: 
const int kPiece[] = { 10,20,30,45,65,90,70,45,40,35,30,25,22,20,19,18,17,16,15 }; 
定時器設定為10ms,每隔10ms,計算一次t=kPiece(0~i) / kPiece(0~N) * T; 
kPiece(0~i)為前i項和,kPiece(0~N)為總和,T為固定的總時間 


2. 飛機朝向 


飛機的頭要隨著曲線改變朝向。很顯然,這個朝向就是曲線的切線方向。 
求切線,正確的方式是求導。 
在這裡,我們選擇了一種簡單的方法: 
記住當前點和上一點,然後計算2點的斜率,再轉換成角度。 


3. 飛機大小 


飛機大小變化沒有嚴格要求,可採取線性變化,在總時間T內從1.0到0.25(根據素材大小決定),恰好做到與曲線同時變化結束。 

特別注意 
I、起飛角度轉換成斜率,在計算機世界,其座標系與數學中的座標系不一致,X軸一致,而Y軸相反,所以角度可能是負數(比如起始時往左飛行) 
II、飛機朝向,斜率轉換成角度時,從A到B,B相對於A的位置可在4個象限,所以角度可能存在負角度,與此同時,素材中飛機也有個角度,那麼繪製時,注意矯正角度 
III、飛機降落時角度可能不正確(如從上方降落)。實際應用時,由於飛機變小且速度較快,而且降落時調整了角度,所以此問題不明顯。如要保證飛機降落角度一定不變,可以考慮三次貝塞爾曲線 

Gdiplus繪製飛機

    Gdiplus::Graphics g(dc);
    Gdiplus::PointF center(cx / 2, cy / 2);  // cx、cy為素材寬度和高度
    g.TranslateTransform(center.X, center.Y);  // 轉換座標系
    g.RotateTransform(angle_);  // 旋轉角度
    g.SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBilinear);
    g.ScaleTransform(scale_, scale_);  // 縮放
    g.TranslateTransform(-center.X, -center.Y);
    g.DrawImage(plane_.get(), 0, 0);  // 繪製圖片


免費領取驗證碼、內容安全、簡訊傳送、直播點播體驗包及雲伺服器等套餐

更多網易技術、產品、運營經驗分享請訪問網易雲社群


相關文章:
【推薦】 Docker容器的原理與實踐(下)
【推薦】 聊聊WS-Federation
【推薦】 NOS服務監控實踐