Đường cong Bézier

Đây là bài viết cung cấp các kiến thức cơ sở nền tảng để hiểu series bài viết về khớp đường cong cho tập điểm.

Chắc hẳn gần như ai đã từng dùng các ứng dụng vẽ hình, thiết kế đồ họa đều đã sử dụng qua công cụ vẽ đường cong dạng thế này:

Một đường cong như trên được gọi là một đường cong Bezier bậc ba, bao gồm 4 control points: hai điểm (hình tròn đen) ở đầu mút và hai điểm điều chỉnh độ uốn (hình vuông xanh). Để cho tiện, ta đặt tên cho 4 điểm này lần lượt là P0,P1,P2,P3, ta có phương trình để vẽ đường cong bậc 3 được định nghĩa như sau:

B(t)=(1t3)P0+3(1t)2P1+3(1t)t2P2+t3P3,1t0

Mình xin phép không đi quá sâu vào lý do tại sao có công thức trên, bạn đọc hoàn toàn có thể tìm thấy nguồn giải thích dễ dàng trên các trang khác.

Ở đây, các điểm P0,P1,P2,P3 được coi như các vector trên mặt phẳng 2 chiều có tọa độ (x,y). Phép nhân các điểm này với một số thực được coi như một phép scale:

tP(x,y)=(x×t,y×t)

Từ công thức tính B(t)ở trên, ta có thể vẽ đường cong Bezier bằng cách:

  • Chia đoạn [0,1]ra thành n phần bằng nhau t0=0,t1=1/n,t2=2/n,,tn=1

  • Tính các giá trị B(ti),i{0,1,,n} để tìm ra một tập hợp điểm nằm trên đường cong: {(x0,y0),(x1,y1),,(xn,yn)}

  • Vẽ các đường thẳng nối các điểm này lại với nhau

Source code Javascript sử dụng P5JS

				
					var Bezier = function(p1, p2, p3, p4){
    this.p1 = p1;
    this.p2 = p2;
    this.p3 = p3;
    this.p4 = p4;

    // chia đường cong thành n đoạn thẳng nhỏ
    // bằng cách tính tọa độ các điểm trên trên đường cong
    // rồi nối toàn bộ lại với nhau
    // nSegs là số đoạn cần chia
    this.segmentation = function(nSegs){
        let inc = 1.0 / nSegs; // increment step
        let t = 0;
        let arr = [];

        for (let i = 0; i < nSegs; i++){
            // các tham số, xem lại công thức tính B(t)
            let t1 = 1 - t;
            let t1_3 = t1*t1*t1;
            let t1_3a = (3*t)*t1*t1;
            let t1_3b = (3*t*t)*t1
            let t1_3c = t*t*t;

            // tính tọa độ x
            let x = t1_3 * this.p1.x
            x = x + t1_3a * this.p2.x;
            x = x + t1_3b * this.p3.x;
            x = x + t1_3c * this.p4.x
            // tính tọa độ y
            let y = t1_3  * this.p1.y;
            y = y + t1_3a * this.p2.y;
            y = y + t1_3b * this.p3.y;
            y = y + t1_3c * this.p4.y;

            // lưu tọa độ vào mảng
            arr.push({"x":x, "y":y});            
            t = t + inc;
        }

        return arr;
    }

    this.draw = function(){
        // chia đường cong thành nhiều đoạn rồi        
        // tính tọa độ các điểm trên đường cong
        let segments = this.segmentation();

        // vẽ các đường thẳng nối các điểm lại với nhau
        for (let i = 0; i < segments.length - 1; i++){
            line(segments[i].x, segments[i].y, segments[i+1].x, segments[i+1].y);
        }
    }
}
				
			

Subscribe to SkyGLab

Scroll to Top