Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
542 views
in Technique[技术] by (71.8m points)

graphics - How to approximate a half-cosine curve with bezier paths in SVG?

Suppose I want to approximate a half-cosine curve in SVG using bezier paths. The half cosine should look like this:

half cosine

and runs from [x0,y0] (the left-hand control point) to [x1,y1] (the right-hand one).

How can I find an acceptable set of coefficients for a good approximation of this function?

Bonus question: how is it possible to generalize the formula for, for example, a quarter of cosine?

Please note that I don't want to approximate the cosine with a series of interconnected segments, I'd like to calculate a good approximation using a Bezier curve.

I tried the solution in comments, but, with those coefficients, the curve seems to end after the second point.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Let's assume you want to keep the tangent horizontal on both ends. So naturally the solution is going to be symmetric, and boils down to finding a first control point in horizontal direction.

I wrote a program to do this:

/*
* Find the best cubic Bézier curve approximation of a sine curve.
*
* We want a cubic Bézier curve made out of points (0,0), (0,K), (1-K,1), (1,1) that approximates
* the shifted sine curve (y = a?sin(bx + c) + d) which has its minimum at (0,0) and maximum at (1,1).
* This is useful for CSS animation functions.
*
*      ↑      P2         P3
*      1      ×???????***×
*      |           ***
*      |         **
*      |        *
*      |      **
*      |   ***
*      ×***???????×------1-→
*      P0         P1
*/

const sampleSize = 10000; // number of points to compare when determining the root-mean-square deviation
const iterations = 12; // each iteration gives one more digit

// f(x) = (sin(π?(x - 1/2)) + 1) / 2 = (1 - cos(πx)) / 2
const f = x => (1 - Math.cos(Math.PI * x)) / 2;

const sum = function (a, b, c) {
  if (Array.isArray(c)) {
      return [...arguments].reduce(sum);
  }
  return [a[0] + b[0], a[1] + b[1]];
};

const times = (c, [x0, x1]) => [c * x0, c * x1];

// starting points for our iteration
let [left, right] = [0, 1];
for (let digits = 1; digits <= iterations; digits++) {
    // left and right are always integers (digits after 0), this keeps rounding errors low
    // In each iteration, we divide them by a higher power of 10
    let power = Math.pow(10, digits);
    let min = [null, Infinity];
    for (let K = 10 * left; K <= 10 * right; K+= 1) { // note that the candidates for K have one more digit than previous `left` and `right`
        const P1 = [K / power, 0];
        const P2 = [1 - K / power, 1];
        const P3 = [1, 1];

        let bezierPoint = t => sum(
            times(3 * t * (1 - t) * (1 - t), P1),
            times(3 * t * t * (1 - t), P2),
            times(t * t * t, P3)
        );

        // determine the error (root-mean-square)
        let squaredErrorSum = 0;
        for (let i = 0; i < sampleSize; i++) {
            let t = i / sampleSize / 2;
            let P = bezierPoint(t);
            let delta = P[1] - f(P[0]);
            squaredErrorSum += delta * delta;
        }
        let deviation = Math.sqrt(squaredErrorSum); // no need to divide by sampleSize, since it is constant

        if (deviation < min[1]) {
            // this is the best K value with ${digits + 1} digits
            min = [K, deviation];
        }
    }
    left = min[0] - 1;
    right = min[0] + 1;
    console.log(`.${min[0]}`);
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...