不幸的是,如果不依赖数学就无法做到这一点,因为从您的图像中,我们需要绘制的曲线类型取决于它所连接的对象。分析您的给定图像显示两条曲线不共享“构造”属性:
红线是开始/结束基线,以及它们拟合曲线的最大平移,蓝线是从开始到第一个控件,第二个控件到终点的方向,绿线大致表示曲线的数学中点(t = 0.5),黑线表示曲线的最大垂直延伸。
为了形成这些曲线,我们将在标准曲线上发布线性代数,看看它会把我们带到哪里。
右边的曲线实际上相对容易构建,因为它是一条非常对称的曲线,我们可以通过缩放标准半圆形贝塞尔曲线来制作:
{ (0,0), (0,0.552), (1,0.552), (1,0) }
这将使曲线直线“向外”,所以让我们扭曲它,使曲线以微小的角度开始和结束:
{ (0,0), (0.2,0.552), (0.8,0.552), (1,0) }
那是在一条直线上,高度为半圆,并且向上,所以我们需要将它缩小到大约四分之一高度,并可能在 y 坐标前贴上一些减号。
{ (0,0), (0.2, +/- 0.138), (0.8, +/- 0.138), (1,0) }
并根据起点 p1 和终点 p4 对其进行缩放以匹配您需要的线长,
D = distance(p1, p4)
{ (0,0), (0.2 * D, 0.138 * D), (0.8 * D, 0.138 * D), (D,0) }
然后我们旋转坐标,使它们位于正确的角度线上,使用您的线与水平线之间的角度,并将该角度粘贴在旋转矩阵中:
phi = atan2(p4.y - p1.y, p4.x - p1.x)
{
(0, 0),
(0.2 * D * cos(phi) - 0.138 * D * sin(phi), 0.2 * D * sin(phi) + 0.138 * D * cos(phi)),
(D * cos(phi) - 0.138 * D * sin(phi), D * sin(phi) + 0.138 * D * sin(phi)),
(D * cos(phi), D * sin(phi)
}
这看起来很“数学”,但事实并非如此。cos(phi) 和 sin(phi),如果你已经有了 phi,只是两个数字,这里不涉及数学,只是愚蠢的算术。
最后一步是翻译所有坐标,使它们位于页面上的正确位置:
{
(p1.x + 0, p1.y + 0),
(p1.x + 0.2 * D * cos(phi) - 0.138 * D * sin(phi), p1.y + 0.2 * D * sin(phi) + 0.138 * D * cos(phi)),
(p1.x + D * cos(phi) - 0.138 * D * sin(phi), p1.y + D * sin(phi) + 0.138 * D * sin(phi)),
(p1.x + D * cos(phi), p1.y + D * sin(phi)
}
完毕。你的第二条曲线很容易制作。
左边的曲线工作量稍大一些,但只是略微。我们——也许不直观——以同样的方式开始,形成与之前完全相同类型的曲线,在旋转之前停止。我们可以观察到,如果我们将图像中的曲线平放在水平面上,它实际上是一个常规缩放的半圆,但向右剪切。所以让我们这样做:
旋转前:
{ (0,0), (0.2 * D, 0.138 * D), (0.8 * D, 0.138 * D), (D,0) }
水平剪切
float sx = <strength of the shear>
{ (0,0), (0.2 * D + 0.138 * D * sx, 0.138 * D), (0.8 * D + 0.138 * D * sx, 0.138 * D), (D,0) }
旋转:
phi = atan2(p4.y - p1.y, p4.x - p1.x)
{
(0, 0),
((0.2 * D + 0.138 * D * sx) * cos(phi) - 0.138 * D * sin(phi), (0.2 * D + 0.138 * D * sx) * sin(phi) + 0.138 * D * cos(phi)),
((D + 0.138 * D * sx) * cos(phi) - 0.138 * D * sin(phi), (D + 0.138 * D * sx) * sin(phi) + 0.138 * D * cos(phi)),
(D * cos(phi), D * sin(phi)
}
然后最后的翻译步骤也是一样的。同样只是主要插入数字,尽管这一次您将不得不使用剪切值来查看哪个看起来最好。
自由参数
我们可以通过改变我们缩放初始半圆的程度来控制曲线的“弯曲”程度。0.25 的系数相对较紧,0.33 的系数相对较高。我们还可以控制像你离开领带这样的曲线的剪切力有多尖锐。1 的剪切是微妙的,1.75 的剪切是非常突然的。
为什么这行得通
贝塞尔曲线,尽管名称中有“曲线”一词,但它是线的线性插值的线性插值。对构建曲线的坐标应用线性变换可以保留曲线的属性,因此我们可以不尝试使用完整的曲线,而是将四个坐标弄乱,并相信曲线看起来是正确的。
因此,我们采用我们知道坐标的曲线的四个坐标,然后应用我们需要的所有变换来获得我们想要的曲线:
(x,y) . scale . (shearx?) . rotation, + (tx,ty)
这是:
|x| . | D 0 | . | 1 shearx | . |cos(phi) -sin(phi)| + |tx|
|y| | 0 D | | 0 1 | |sin(phi) cos(phi)| |ty|
并且矩阵运算可以折叠成单个矩阵(这就是为什么计算机如此擅长 2D/3D 的原因——它们都只是矩阵,所以非常复杂的运算仍然只是应用于一百万个坐标的单个矩阵)。
实际上,如果我们将坐标视为 3d 坐标,我们甚至可以将平移作为矩阵运算进行,z 值始终为 1。但这与您的问题不再真正相关。
一个jsiddle
可以在http://jsfiddle.net/CLbUF/1找到逐步实现左领带的小提琴,但这并没有将所有操作折叠成一个操作。你需要自己做。