如何在SVG中使用贝塞尔曲线近似半余弦曲线?

时间:2015-03-12 23:46:43

标签: svg graphics trigonometry bezier spline

假设我想使用贝塞尔曲线近似SVG中的半余弦曲线。半余弦应该是这样的:

half cosine

从[x0,y0](左手控制点)到[x1,y1](右手控制点)运行。

如何找到一组可接受的系数,以便很好地逼近该函数?

奖金问题:如何推广公式,例如四分之一的余弦?

请注意,我希望通过一系列相互关联的段来近似余弦,我想使用贝塞尔曲线计算好的近似值。

我在评论中尝试了解决方案,但是,使用这些系数,曲线似乎在第二点之后结束。

4 个答案:

答案 0 :(得分:13)

假设您想要在两端保持切线水平。因此,解决方案自然会是对称的,归结为在水平方向上找到第一个控制点。

我写了一个程序来做到这一点:



$(document).ajaxStart(
    function () {
        $('.ajax-loader').css("visibility", "visible");
    }
).ajaxStop(
    function () {
        $('.ajax-loader').css("visibility", "hidden");
    }
);




为简化计算,我使用标准化的正弦曲线,该曲线通过/* * 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]}`); }(0,0)作为其最小/最大点。这对CSS动画也很有用。

它返回 (1,1) * 作为具有最小均方根偏差的点(约(.3642124232,0))。

我还创建了一个显示准确性的Desmos graph

Desmos Graph (sine approximation with cubic Bézier curve) (单击以试用它 - 您可以左右拖动控制点)

* 请注意,使用JS进行数学运算时存在舍入误差,因此该值可能精确到不超过5位左右。

答案 1 :(得分:6)

经过几次尝试/错误后,我发现正确的比例 K = 0.37

"M" + x1 + "," + y1
+ "C" + (x1 + K * (x2 - x1)) + "," + y1 + ","
+ (x2 - K * (x2 - x1)) + "," + y2 + ","
+ x2 + "," + y2

查看此示例以了解Bezier如何与余弦匹配:http://jsfiddle.net/6165Lxu6/

绿线是真正的余弦,黑色是贝塞尔。向下滚动以查看5个样本。每次刷新时点都是随机的。

为了概括,我建议使用剪辑。

答案 2 :(得分:6)

由于贝塞尔曲线不能精确地重建正弦曲线,因此有许多方法可以创建近似值。我假设我们的曲线从点(0,0)开始,到(1,1)结束。

简单方法

解决这个问题的一个简单方法是使用控制点(K,0)和((1-K),1)构造Bezier曲线 B ,因为所涉及的对称性和希望在t = 0和t = 1时保持水平切线。

然后我们只需要找到K的值,使得Bezier曲线的导数与t = 0.5时的正弦曲线的导数匹配,即pi / 2

由于我们derivativeBezier curve\frac{dy}{dx} = \frac{dy/dt}{dx/dt} = \frac{d(3(1-t)t^2+t^3)/dt}{d(3(1-t)^2tK+3(1-t)t^2(1-K)+t^3)/dt}提供,因此在t = 0.5时简化为d

将此值设置为我们所需的导数,我们得到解K=\frac{\pi-2}{\pi}\approx0.36338022763241865692446494650994255\ldots

因此,我们的近似结果为:

cubic-bezier(0.3633802276324187, 0, 0.6366197723675813, 1)

它非常接近,均方根偏差约为0.000224528:

cubic bezier approximation compared with sinusoidal

高级方法

为了更好地逼近,我们可能希望最小化它们的差异root mean square。计算起来比较复杂,因为我们现在试图在区间(0,1)中找到K的值,以最小化以下表达式:

\int ^{1}_{0} \left( B_y\left(t\right) - \frac{ 1 - \cos \left( \pi B_x \left( t \right) \right) }{2} \right)^2 B_x'\left( t \right) dt

其中B的定义如下:

B_x(t) = 3\left(1-t\right)^2tK+3\left(1-t\right)t^2\left(1-K\right)+t^3; B_y(t) = 3\left(1-t\right)t^2+t^3

cubic-bezier(0.364212423249, 0, 0.635787576751, 1)

答案 3 :(得分:1)

我建议阅读本文关于贝塞尔曲线和椭圆的数学,因为这基本上是你想要的(画一个椭圆的一部分): http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf

它提供了一些所需的见解。

然后看看这个图: http://www.svgopen.org/2003/papers/AnimatedMathematics/ellipse.svg

其中为椭圆做了一个例子

现在你得到了数学,请在LUA中看到这个例子;) http://commons.wikimedia.org/wiki/File:Harmonic_partials_on_strings.svg

...多田