在贝塞尔曲线中放置“好”控制点

时间:2010-09-22 17:17:33

标签: objective-c geometry bezier

我一直在研究这个问题已经有一段时间了,而且还没能找到一个好的解决方案。

问题:我有一个有三个(或更多)2D点的有序列表,我希望用一个三次贝塞尔曲线描绘它们,使它“看起来很好”。 “看起来不错”的部分非常简单:我只是希望第二点处的楔形变得平滑(例如,曲线本身不会双回)。因此,给定三点,在绘制曲线时,应该将两个控制点放置在三元组中的第二个点周围。

到目前为止我的解决方案如下,但不完整。这个想法也可能有助于传达我所追求的外观。

给出三个点,(x1,y1),(x2,y2),(x3,y3)。取每个三元组点刻的圆(如果它们是共线的,我们只是在它们之间绘制一条直线并继续前进)。在点(x2,y2)处取与该圆相切的直线 - 我们将围绕(x2,y2)的控制点放在该切线上。

这是我坚持的最后一部分。我遇到的问题是找到一种方法将两个控制点放在这条切线上 - 我有一个足够好的启发式算法,它应该在这条线上距离(x2,y2)有多远,但当然,还有这条线上的两个点就是那个距离。如果我们在“错误”的方向上计算一个,那么曲线就会自行循环。

要找到由三个点描述的圆的中心(如果任何一个点具有相同的x值,只需对下面计算中的点重新排序):

double ma = (point2.y - point1.y) / (point2.x - point1.x);
double mb = (point3.y - point2.y) / (point3.x - point2.x);
CGPoint c; // Center of a circle passing through all three points.
c.x = (((ma * mb * (point1.y - point3.y)) + (mb * (point1.x + point2.x)) - (ma * (point2.x + point3.x))) / (2 * (mb - ma)));
c.y = (((-1 / ma) * (c.x - ((point1.x + point2.x) / 2))) + ((point1.y + point2.y) / 2));

然后,要找到切线上的点,在这种情况下,找到从point2到point3的曲线的控制点:

double d = ...; // distance we want the point. Based on the distance between
                // point2 and point3.
// mc: Slope of the line perpendicular to the line between
// point2 and c.
double mc = - (c.x - point2.x) / (c.y - point2.y);
CGPoint tp; // point on the tangent line
double c = point2.y - mc * point2.x; // c == y intercept
tp.x = ???; // can't figure this out, the question is whether it should be
            // less than point2.x, or greater than?
tp.y = mc * tp.x + c;
// then, compute a point cp that is distance d from point2 going in the direction
// of tp.

2 个答案:

答案 0 :(得分:0)

听起来你可能需要弄清楚曲线的方向,以便设置切点,使其不会自身加倍。根据我的理解,只需找出从(x1, y1)(x2, y2)的方向,然后在最接近(x1, y1) -> (x2, y2)方向的方向上沿着切线行驶你的启发距离,把切点放在那里。

答案 1 :(得分:0)

如果你真的相信你有一个很好的方法可以选择你的点应该在切线上的距离,而你只需要决定将每一个放在哪一侧,那么我建议你看一次再次在该线相切的那个圆上。你在圆圈上按顺序得到了z1,z2,z3;想象一下绕着从z2到z1的圆圈,但是沿着切线走;控制点“在z2之前”的哪一侧应该是;控制点“在z2之后”应该在另一边。

请注意,这保证始终将两个控制点放在z2的两侧,这很重要。 (另外:你可能希望它们与z2的距离相同,因为否则你会在z2中得到一个不连续性,呃,你的曲线的二阶导数,这可能看起来有点不理想。)我打赌会有仍然是病态病例。

如果您不介意相当多的代码复杂性,那么在Don Knuth的METAFONT程序(其主要目的是绘制字体)中,有一个复杂且非常有效的算法可以准确解决您的问题(以及更多)。该算法归功于John Hobby。您可以在METAFONT中找到详细的解释和工作代码,或者更好的是,密切相关的METAPOST(生成PostScript输出而不是巨大的位图)。

虽然指向你有点棘手,因为METAFONT和METAPOST是“识字程序”,这意味着他们的源代码和文档包含一种Pascal代码(用于METAFONT)或C代码(用于METAPOST)和TeX标记。有些程序会把它变成一个精美的排版文档,但据我所知,没有人把结果放在网上任何地方。所以这里是源代码的链接,您可能会发现或者可能找不到完全不可理解的内容:http://foundry.supelec.fr/gf/project/metapost/scmsvn/?action=browse&path=%2Ftrunk%2Fsource%2Ftexk%2Fweb2c%2Fmplibdir%2Fmp.w&view=markup - 您应该在其中搜索“选择控制点”。

(METAFONT的精美排版文档可以作为正确装订的书籍,标题为“METAFONT:程序”。但它需要花费实际金钱,代码是Pascal。)