分段贝塞尔曲线是否通过垂直线测试?

问题描述

考虑具有 N 个段的分段三次贝塞尔曲线,根据 4N 个控制点定义。如何确定这条曲线是否通过了垂直线测试?也就是说:是否存在点 x,y1,y2 使得 y1!=y2 并且 (x,y1) 和 (x,y2) 都位于曲线上?此外,如果这些点存在,返回点 x,y2 的值​​会很好但不是必需的。

原则上,您可以只在大量点上明确评估曲线,但在我的应用程序中,曲线可能有大量段,因此速度非常慢。因此,我正在寻找一种仅在控制点上运行的算法,而不依赖于在大量点上明确评估曲线。

解决方法

根据定义,闭合曲线会使您的测试失败,所以让我们看一下开放曲线:对于无法通过垂直线测试的曲线,分段三次 polyBezier 中的至少一个线段需要沿 x 轴“反转方向”,这意味着它的 x 分量需要在包含区间 [0,1] 内 have extrema

例如,这条曲线没有(右侧显示 x 的 component function,全局极值为红色,拐点为紫色):

A cubic Bezier without multiple y values for x values

(另请注意,我们只绘制了 [0,1] 区间,但如果我们扩展它,那些全局极值实际上不会位于 t=0t=1。这实际上非常重要,我们将在下面看到原因)

这条曲线也没有(但只有只是):

enter image description here

但这条曲线确实:

enter image description here

这个也是。事实上两次:

enter image description here

就像曲线“刚过它的尖端”一样:

enter image description here

当我们夸大它时更容易看出:

enter image description here

这意味着我们需要找到一阶导数,找出它在哪里为零,然后确保(因为贝塞尔曲线可能有尖点)该导数的符号在那个点(或点,因为可以有两个)翻转.

事实证明,导数是 trivially not-even-really-computed,所以对于每个段,我们有:

w = [x1,x2,x3,x4]

Bx(t) = w[0] * (1-t)³ + 3 * w[1] * t(1-t)² + w[2] * t²(1-t) + w[3] * t³

// bezier form of the derivative:

v = [
  3 * (w[1] - w[0]),3 * (w[2] - w[1]),3 * (w[3] - w[2]),]

Bx'(t) = v[0] * (1-t)² + 2 * v[1] * t(1-t) + v[2] * t²

我们可以简单地重写为多项式形式:

a = v[0] - 2*v[1] + v[2]
b = 2 * (v[1] - v[0])
c = v[0]

Bx'(t) = a * t² + b * t + c

所以我们找到了它的根,这就是应用 quadratic formula 的问题,它给了我们 0、1 或 2 个根。

if (a == 0) there are no roots

denominator = 2 * a
discriminant = b * b - 2 * denominator * c

if (discriminant < 0) there are no (real) roots

d = sqrt(discriminant)

t1 = -(b + d) / denominator
if (0 ≤ t1 ≤ 1) then t1 is a valid root

t2 = (d - b) / denominator
if (0 ≤ t2 ≤ 1) then t2 is a valid root

如果没有(实数)根,则该段不会导致曲线无法通过您的垂直线测试,我们将继续进行下一段并重复,直到我们发现失败,或者我们已经要测试的段用完了。

如果是 1 或 2 个根,我们检查 Bx'(t-ε)Bx'(t+ε) 的符号对于一些非常小的 ε 值是否不同(因为我们想确保我们不会得出上述结论带尖头的曲线未通过测试:它的导数为零,但它“继续沿相同方向”穿过该根而不是翻转方向)。如果是,则该线段是使您的曲线无法通过垂直线测试的线段之一。

另外,请注意,即使根在 0 或 1 处,我们也在进行测试:分段曲线可能会“跨段”弯曲,我们可以利用这样一个事实:我们可以评估 Bx'(t) 用于 t = -εt = 1+ε 以查看我们是否翻转方向,即使我们从未在 t=0 之前或 {{1} 之后绘制曲线}}。

,

将解决方案留在这里以供参考。不是特别优雅,但可以完成工作。假设曲线从左到右参数化。关键观察是曲线在两点与垂线相交当且仅当存在 x 分量的导数 x'(t) 严格为负的点。对于三次贝塞尔曲线,导数是二次多项式 x'(t)=at^2+bt+c。所以我们只需要检查这个二次方在区间 0