?在兩條折線間完成平滑的過渡是 用畫布做UI 或者做類似地圖編輯器一類的工作的 很常見的任務。
怎么樣化方為圓是決定工作效率的很重要的因素。(當需要編輯的曲線多起來, 復雜起來的時候,這會是件相當繁重的工作)
最容易想到的莫非是 貝塞爾曲線,而且時下幾乎所有主流的數學算法庫或者畫布api 都已經很好的支持了貝塞爾曲線的繪制。 并能提供很便利的接口,通常只需知道 開始位置, 結束位置 ,以及貝塞爾控制點 就可生成一條貝塞爾曲線。
例如:
context.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, endX, endY);
貝塞爾曲線的定義是由當前的上下文來看,兩個控制點,和結束點。曲線的第一部分是切向的假想線,是由上下文點和第一個控制點定義。第二部分的曲線相切的假想線,是第二個控制點和結束點定義。
但是貝塞爾曲線仍舊屬于“高輸入”api, 尤其是貝塞爾控制點的定義,其實并不是一個簡單就能得到的東西。
而且在實際應用中,通常的需求多為 已知折線,然后把折線轉化為曲線的情況。
一般來說,折線我們是最好獲得的,得到轉折點連接起來就可以了。
但是當折線多起來的時候,會發現按照貝塞爾曲線繪制的思路來繪制平滑的曲線會是一件相當困難的事,光是控制點的確定就是一個大工程。
為了更好的,直接的把折線轉化為平滑的曲線。我們還是得回到曲線繪制的根源--- 折線模擬
這就是今天想記錄的Chaikin Curve, 插值曲線。
原理:
我們稱一條折線的起始和結束的點 為斷點, 中間的轉折點都叫折點。
于是, 在每個非端點的折點附近取連接這個折點的兩條線段各自的1/3位置處新建一個點。并將這兩個新建的點連接。
如此重復下去,直到模擬出想要的平滑度。
根據想要的不同的曲線弧度,也可將1/3 替換成 1/4 或1/2 ...
這種方法如此簡單,已知一條折線,根據需要定義迭代次數即可得到一條曲線(模擬)。
同時,用這種方式創建的折線還有一個優勢,就是很方便的能得到曲線的長度,因為是折線模擬的,所以折線長度之和即為曲線的長度。
也不用擔心這種類似二分的迭代方式計算量會有多大。我自己的測試,在800*800的區域通過插值來模擬曲線,基本也就迭代4次就已經足夠平滑了。
曲線模擬的過程大致如下的動畫:
以上都為隨機6個點,迭代模擬4次的過程。
主函數即為下面的 subDivide
var Point2 = La.geometry.Point2, // Point Class
Vector2 = La.geometry.Vector2, // Vector Class
self = this;
this.subDivide = function (handles, subdivs) {
if (handles.length) {
do {
var numHandles = handles.length;
// 第一個點
handles.push(new Point2(handles[0].x, handles[0].y));
for (var i = 0; i < numHandles - 1; ++i) {
// 每次拿出兩個點
var p0 = handles[i];
var p1 = handles[i + 1];
// 根據兩個原始點創建兩個新點,做插值
var Q = new Point2(0.75 * p0.x + 0.25 * p1.x, 0.75 * p0.y + 0.25 * p1.y);
var R = new Point2(0.25 * p0.x + 0.75 * p1.x, 0.25 * p0.y + 0.75 * p1.y);
handles.push(Q);
handles.push(R);
}
// 最后一個店
handles.push(new Point2(handles[numHandles - 1].x, handles[numHandles - 1].y));
// 更新數組
for (var i = 0; i < numHandles; ++i)
handles.shift();
//handles.shift(numHandles);
} while (--subdivs > 0);
}
};