본문으로 건너뛰기

3차 베지어 곡선의 제어점 구하기

무료2015-09-06#Math#JS#Solution#平滑曲线#贝塞尔曲线#三次贝塞尔曲线#Bezier Control Point#贝塞尔曲线控制点#JavaScript贝塞尔曲线

현재 canvas는 최대 3차 베지어 곡선까지 지원합니다. 본 글에서는 계산량이 적고 간단하며 사용하기 쉬운 제어점을 구하는 완벽한 방법을 상세히 소개합니다.

들어가며

처음에는 전통적인 막대형 스펙트럼 차트를 부드러운 곡선으로 연결하고 싶었습니다. "호흡하는 듯한" 효과가 더 자연스럽게 느껴질 것이라 생각했기 때문입니다.

이전에 최소제곱법(Least Squares Method) 곡선 피팅을 시도해 보았지만, 결론은 최소제곱법 곡선 피팅으로는 이 문제를 해결할 수 없다는 것이었습니다.

  • 최소제곱법 곡선 피팅: 모든 산점도를 균등하게 두 부분으로 나누는 직선 또는 n차 곡선을 구할 수 있습니다.

  • 베지어 곡선: 각 산점도를 부드럽게 통과하는 곡선을 구할 수 있습니다.

1. 문제 재정의

3차 베지어 곡선은 아래 그림과 같이 2개의 제어점이 필요합니다.

[caption id="attachment_751" align="alignnone" width="360"]3차 베지어 곡선 3차 베지어 곡선[/caption]

(그림의 P1, P2가 제어점입니다)

Audio API를 통해 각 산점도(막대 스펙트럼 차트의 각 막대 상단)를 얻을 수 있지만, 어려운 점은 2개의 제어점을 구하는 것입니다. 임의로 정하면 효과가 매우 좋지 않습니다.

2. 해결 방안

경험적 실무에 기반한 해결 방안을 찾았습니다. 엄격한 수학적 근거는 없지만, 실제 효과는 완벽합니다. 구체적인 단계는 다음과 같습니다.

  1. 제어점이 (x1, y1)과 (x2, y2) 사이에 있다고 가정합니다. 첫 번째 점과 마지막 점은 각각 곡선 경로상의 이전 점과 다음 점입니다.

  2. 중점을 구합니다.

  3. 각 중점을 연결하는 선의 길이를 구합니다.

  4. 중점 연결선의 길이 비율을 구합니다 (평행 이동 전 p2, p3의 위치를 결정하는 데 사용됩니다).

  5. p2를 평행 이동합니다.

  6. p3를 평행 이동합니다.

  7. [선택 사항] 제어점과 정점 사이의 거리를 미세 조정합니다. 거리가 멀수록 곡선이 더 곧아집니다.

더 자세한 내용은 Interpolation with Bezier Curves A very simple method of smoothing polygons를 확인하세요.

부드러운 효과는 简书:Android手写优化-更为平滑的签名效果实现에서 확인할 수 있습니다.

3. JavaScript로 3차 베지어 곡선의 제어점 구하기

원판은 Java로 구현되어 있으며, 필자가 다음과 같이 간단히 수정하여 캡슐화했습니다.

window.Bezier = {
    /**
     * 获取控制点坐标
     * @param  {Array} arr 4个点坐标数组
     * @param  {Float} smooth_value [0, 1] 平滑度
     *   p1 上一个点
     *   p2 左端点
     *   P3 右端点
     *   p4 下一个点
     * @return {Array}     2个点坐标数组
     */
    getControlPoints: function(arr, smooth_value) {
        var x0 = arr[0].x, y0 = arr[0].y;
        var x1 = arr[1].x, y1 = arr[1].y;
        var x2 = arr[2].x, y2 = arr[2].y;
        var x3 = arr[3].x, y3 = arr[3].y;

        // Assume we need to calculate the control
        // points between (x1,y1) and (x2,y2).
        // Then x0,y0 - the previous vertex,
        //      x3,y3 - the next one.
        // 1.假设控制点在(x1,y1)和(x2,y2)之间,第一个点和最后一个点分别是曲线路径上的上一个点和下一个点

        // 2.求中点
        var xc1 = (x0 + x1) / 2.0;
        var yc1 = (y0 + y1) / 2.0;
        var xc2 = (x1 + x2) / 2.0;
        var yc2 = (y1 + y2) / 2.0;
        var xc3 = (x2 + x3) / 2.0;
        var yc3 = (y2 + y3) / 2.0;

        // 3.求各中点连线长度
        var len1 = Math.sqrt((x1-x0) * (x1-x0) + (y1-y0) * (y1-y0));
        var len2 = Math.sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1));
        var len3 = Math.sqrt((x3-x2) * (x3-x2) + (y3-y2) * (y3-y2));

        // 4.求中点连线长度比例(用来确定平移前p2, p3的位置)
        var k1 = len1 / (len1 + len2);
        var k2 = len2 / (len2 + len3);

        // 5.平移p2
        var xm1 = xc1 + (xc2 - xc1) * k1;
        var ym1 = yc1 + (yc2 - yc1) * k1;

        // 6.平移p3
        var xm2 = xc2 + (xc3 - xc2) * k2;
        var ym2 = yc2 + (yc3 - yc2) * k2;

        // Resulting control points. Here smooth_value is mentioned
        // above coefficient K whose value should be in range [0...1].
        // 7.微调控制点与顶点之间的距离,越大曲线越平直
        var ctrl1_x = xm1 + (xc2 - xm1) * smooth_value + x1 - xm1;
        var ctrl1_y = ym1 + (yc2 - ym1) * smooth_value + y1 - ym1;

        var ctrl2_x = xm2 + (xc2 - xm2) * smooth_value + x2 - xm2;
        var ctrl2_y = ym2 + (yc2 - ym2) * smooth_value + y2 - ym2;

        return [{x: ctrl1_x, y: ctrl1_y}, {x: ctrl2_x, y: ctrl2_y}];
    }
};

주석이 상당히 상세하므로 위에서 언급한 Interpolation with Bezier Curves A very simple method of smoothing polygons와 함께 보시면 이해하는 데 도움이 될 것입니다.

4. 결과 스크린샷

[caption id="attachment_752" align="alignnone" width="794"]베지어 곡선 효과 베지어 곡선 효과[/caption]

부드러운 효과가 뛰어나며 계산량이 적어 실시간 그래픽 렌더링의 요구사항을 충족할 수 있습니다 (원본 프로젝트는 Android 수기 서명입니다).

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성