假设我想使用贝塞尔曲线近似SVG中的半余弦曲线。半余弦应该是这样的:
从[x0,y0](左手控制点)到[x1,y1](右手控制点)运行。
如何找到一组可接受的系数,以便很好地逼近该函数?
奖金问题:如何推广公式,例如四分之一的余弦?
请注意,我不希望通过一系列相互关联的段来近似余弦,我想使用贝塞尔曲线计算好的近似值。
我在评论中尝试了解决方案,但是,使用这些系数,曲线似乎在第二点之后结束。
答案 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:
* 请注意,使用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时的正弦曲线的导数匹配,即。
由于我们derivative的Bezier curve由提供,因此在t = 0.5时简化为。
将此值设置为我们所需的导数,我们得到解
因此,我们的近似结果为:
cubic-bezier(0.3633802276324187, 0, 0.6366197723675813, 1)
它非常接近,均方根偏差约为0.000224528:
高级方法
为了更好地逼近,我们可能希望最小化它们的差异root mean square。计算起来比较复杂,因为我们现在试图在区间(0,1)中找到K的值,以最小化以下表达式:
其中B的定义如下:
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
...多田