检查两条三次 Bézier 曲线是否相交

发布于 2024-09-29 02:09:24 字数 2690 浏览 0 评论 0原文

对于个人项目,我需要找出两条三次贝塞尔曲线是否相交。我不需要知道在哪里:我只需要知道他们是否知道。不过,我需要尽快完成。

我一直在这个地方搜寻并找到了一些资源。大多数情况下,这个问题有一个有希望的答案。

因此,在我弄清楚什么是西尔维斯特矩阵之后,什么是行列式,什么是 结果为什么它有用,我想我知道该解决方案是如何工作的。然而,现实却与此不同,而且效果并不那么好。


闲逛

我使用图形计算器绘制了两条相交的贝塞尔样条线(我们将其称为 B0 和 B1)。它们的坐标如下(P0, P1, P2, P3):

(1, 1) (2, 4) (3, 4) (4, 3)
(3, 5) (3, 6) (0, 1) (3, 1)

结果是以下,B0 是“水平”曲线,B1 是另一条:

相交的两条三次贝塞尔曲线

按照上述问题的得票最高的答案的指示,我将 B0 减去 B1。它给我留下了两个方程(X 轴和 Y 轴),根据我的计算器,它们是:

x = 9t^3 - 9t^2 - 3t + 2
y = 9t^3 - 9t^2 - 6t + 4

西尔维斯特矩阵

并据此我构建了以下西尔维斯特矩阵:

9  -9  -3   2   0   0
0   9  -9  -3   2   0
0   0   9  -9  -3   2
9  -9  -6   4   0   0
0   9  -9  -6   4   0
0   0   9  -9  -6   4

之后,我创建了一个 C++ 函数来计算使用拉普拉斯展开的矩阵行列式:

template<int size>
float determinant(float* matrix)
{
    float total = 0;
    float sign = 1;
    float temporaryMatrix[(size - 1) * (size - 1)];
    for (int i = 0; i < size; i++)
    {
        if (matrix[i] != 0)
        {
            for (int j = 1; j < size; j++)
            {
                float* targetOffset = temporaryMatrix + (j - 1) * (size - 1);
                float* sourceOffset = matrix + j * size;
                int firstCopySize = i * sizeof *matrix;
                int secondCopySize = (size - i - 1) * sizeof *matrix;
                memcpy(targetOffset, sourceOffset, firstCopySize);
                memcpy(targetOffset + i, sourceOffset + i + 1, secondCopySize);
            }
            float subdeterminant = determinant<size - 1>(temporaryMatrix);
            total += matrix[i] * subdeterminant * sign;
        }
        sign *= -1;
    }
    return total;
}

template<>
float determinant<1>(float* matrix)
{
    return matrix[0];
}

它似乎在相对较小的矩阵(2x2、3x3)上工作得很好和 4x4),所以我希望它也适用于 6x6 矩阵。不过,我没有进行广泛的测试,它有可能已损坏。


问题

如果我正确理解了另一个问题的答案,行列式应该为 0,因为曲线相交。然而,将我上面制作的西尔维斯特矩阵输入到我的程序中,它是-2916。

这是我的错误还是他们的错误?确定两条三次贝塞尔曲线是否相交的正确方法是什么?

For a personal project, I'd need to find out if two cubic Bézier curves intersect. I don't need to know where: I just need to know if they do. However, I'd need to do it fast.

I've been scavenging the place and I found several resources. Mostly, there's this question here that had a promising answer.

So after I figured what is a Sylvester matrix, what is a determinant, what is a resultant and why it's useful, I thought I figured how the solution works. However, reality begs to differ, and it doesn't work so well.


Messing Around

I've used my graphing calculator to draw two Bézier splines (that we'll call B0 and B1) that intersect. Their coordinates are as follow (P0, P1, P2, P3):

(1, 1) (2, 4) (3, 4) (4, 3)
(3, 5) (3, 6) (0, 1) (3, 1)

The result is the following, B0 being the "horizontal" curve and B1 the other one:

Two cubic Bézier curves that intersect

Following directions from the aforementioned question's top-voted answer, I've subtracted B0 to B1. It left me with two equations (the X and the Y axes) that, according to my calculator, are:

x = 9t^3 - 9t^2 - 3t + 2
y = 9t^3 - 9t^2 - 6t + 4

The Sylvester Matrix

And from that I've built the following Sylvester matrix:

9  -9  -3   2   0   0
0   9  -9  -3   2   0
0   0   9  -9  -3   2
9  -9  -6   4   0   0
0   9  -9  -6   4   0
0   0   9  -9  -6   4

After that, I've made a C++ function to calculate determinants of matrices using Laplace expansion:

template<int size>
float determinant(float* matrix)
{
    float total = 0;
    float sign = 1;
    float temporaryMatrix[(size - 1) * (size - 1)];
    for (int i = 0; i < size; i++)
    {
        if (matrix[i] != 0)
        {
            for (int j = 1; j < size; j++)
            {
                float* targetOffset = temporaryMatrix + (j - 1) * (size - 1);
                float* sourceOffset = matrix + j * size;
                int firstCopySize = i * sizeof *matrix;
                int secondCopySize = (size - i - 1) * sizeof *matrix;
                memcpy(targetOffset, sourceOffset, firstCopySize);
                memcpy(targetOffset + i, sourceOffset + i + 1, secondCopySize);
            }
            float subdeterminant = determinant<size - 1>(temporaryMatrix);
            total += matrix[i] * subdeterminant * sign;
        }
        sign *= -1;
    }
    return total;
}

template<>
float determinant<1>(float* matrix)
{
    return matrix[0];
}

It seems to work pretty well on relatively small matrices (2x2, 3x3 and 4x4), so I'd expect it to work on 6x6 matrices too. I didn't conduct extensive tests however, and there's a possibility that it's broken.


The Problem

If I understood correctly the answer from the other question, the determinant should be 0 since the curves intersect. However, feeding my program the Sylvester matrix I made above, it's -2916.

Is it a mistake on my end or on their end? What's the correct way to find out if two cubic Bézier curves intersect?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

我早已燃尽 2024-10-06 02:09:24

贝塞尔曲线的交点是通过(非常酷的)Asymptote矢量图形语言完成的:查找 intersect( ) 此处

尽管他们没有解释他们实际使用的算法,只是说它来自 p.在“The Metafont Book”的第 137 页中,它的关键似乎是贝塞尔曲线的两个重要属性(尽管我现在找不到该页面,但该网站的其他地方对此进行了解释):

  • 贝塞尔曲线始终包含在由 4 个控制点定义的边界框
  • 一条贝塞尔曲线始终可以在任意 t 值处细分为 2 条子贝塞尔曲线

借助这两个属性和相交多边形的算法,您可以递归到任意精度:

bezInt(B1, B2):

  1. bbox(B1) 是否与 bbox(B2) 相交?
    • 否:返回 false。
    • 是:继续。
  2. 是面积(bbox(B1)) + 面积(bbox(B2)) <临界点?
    • 是:返回 true。
    • 否:继续。
  3. 将 B1 拆分为 B1a 和 B1b,条件 t = 0.5
  4. 拆分 B2t = 0.5 处转换为 B2a 和 B2b
  5. 返回 bezInt(B1a, B2a )||
    bezInt(B1a, B2b) ||
    bezInt(B1b, B2a) ||
    bezInt(B1b, B2b).

如果曲线不相交,这会很快——这是通常的情况吗?

[编辑]看起来将贝塞尔曲线一分为二的算法称为de Casteljau 算法

Intersection of Bezier curves is done by the (very cool) Asymptote vector graphics language: look for intersect() here.

Although they don't explain the algorithm they actually use there, except to say that it's from p. 137 of "The Metafont Book", it appears that the key to it is two important properties of Bezier curves (which are explained elsewhere on that site though I can't find the page right now):

  • A Bezier curve is always contained within the bounding box defined by its 4 control points
  • A Bezier curve can always be subdivided at an arbitrary t value into 2 sub-Bezier curves

With these two properties and an algorithm for intersecting polygons, you can recurse to arbitrary precision:

bezInt(B1, B2):

  1. Does bbox(B1) intersect bbox(B2)?
    • No: Return false.
    • Yes: Continue.
  2. Is area(bbox(B1)) + area(bbox(B2)) < threshold?
    • Yes: Return true.
    • No: Continue.
  3. Split B1 into B1a and B1b at t = 0.5
  4. Split B2 into B2a and B2b at t = 0.5
  5. Return bezInt(B1a, B2a) ||
    bezInt(B1a, B2b) ||
    bezInt(B1b, B2a) ||
    bezInt(B1b, B2b).

This will be fast if the curves don't intersect -- is that the usual case?

[EDIT] It looks like the algorithm for splitting a Bezier curve in two is called de Casteljau's algorithm.

耳根太软 2024-10-06 02:09:24

如果您要为生产代码执行此操作,我建议使用贝塞尔裁剪算法。 此免费在线 CAGD 文本的第 7.7 节(pdf)对此进行了很好的解释,适用于任何贝塞尔曲线的次数,并且快速且鲁棒。

虽然从数学角度来看,使用标准寻根器或矩阵可能更直接,但贝塞尔剪裁相对容易实现和调试,并且实际上具有更少的浮点误差。这是因为每当它创建新数字时,它都会进行加权平均值(凸组合),因此没有机会根据噪声输入进行推断。

If you're doing this for production code, I'd suggest the Bezier clipping algorithm. It's explained well in section 7.7 of this free online CAGD text (pdf), works for any degree of Bezier curve, and is fast and robust.

While using standard rootfinders or matrices might be more straightforward from a mathematical point of view, Bezier clipping is relatively easy to implement and debug, and will actually have less floating point error. This is because whenever it's creating new numbers, it's doing weighted averages (convex combinations) so there's no chance of extrapolating based on noisy inputs.

月寒剑心 2024-10-06 02:09:24

这是我的错误还是他们的错误?

您对行列式的解释是否基于此答案所附的第四条评论?如果是这样,我相信这就是错误所在。在此转载评论:

如果行列式为零,则有
X 和 Y 的根 * 完全相同
t 的值,因此有
两条曲线的交点。 (t
可能不在区间 0..1 内
不过)。

我不认为这部分有任何问题,但作者接着说:

如果行列式是<>零你可以
确保曲线不会
任意位置相交。

我认为这是不正确的。两条曲线完全有可能在 t 值不同的位置相交,在这种情况下,即使矩阵具有非零行列式,也会存在相交。我相信这就是您的情况。

Is it a mistake on my end or on their end?

Are you basing your interpretation of the determinant on the 4th comment attached to this answer? If so, I believe that's where the mistake lies. Reproducing the comment here:

If the determinant is zero there is is
a root in X and Y at *exactly the same
value of t, so there is an
intersection of the two curves. (the t
may not be in the interval 0..1
though).

I don't see any problems with this part, but the author goes on to say:

If the determinant is <> zero you can
be sure that the curves don't
intersect anywhere.

I don't think that's correct. It's perfectly possible for the two curves to intersect in a location where the t values differ, and in that case, there will be an intersection even though the matrix has a non-zero determinant. I believe this is what's happening in your case.

苍景流年 2024-10-06 02:09:24

这是一个难题。我会将 2 条贝塞尔曲线分成 5-10 条离散线段,然后只进行线与线的相交。

在此处输入图像描述

foreach SampledLineSegment line1 in Bezier1
    foreach SampledLineSegment line2 in Bezier2
        if( line1 intersects line2 )
            then Bezier1 intersects Bezier2

This is a hard problem. I would split each of the 2 Bezier curves into say 5-10 discrete line segments, and just do line-line intersections.

enter image description here

foreach SampledLineSegment line1 in Bezier1
    foreach SampledLineSegment line2 in Bezier2
        if( line1 intersects line2 )
            then Bezier1 intersects Bezier2
人│生佛魔见 2024-10-06 02:09:24

我不知道它有多快,但如果你有两条曲线 C1(t) 和 C2(k),如果 C1(t) == C2(k),它们就会相交。因此,对于两个变量 (t, k),您有两个方程(每个 x 和每个 y)。您可以使用数值方法求解该系统,并且精度足够高。当你找到 t,k 参数时,你应该检查 [0, 1] 上是否有参数。如果是,它们在 [0, 1] 上相交。

I don't know how fast it will be, but if you have two curves C1(t) and C2(k) they intersect if C1(t) == C2(k). So you have two equations (per x and per y) for two variables (t, k). You can solve the system using numerical methods with enough for you accuracy. When you've found t,k parameters you should check if there is a parameter on [0, 1]. If it is they intersects on [0, 1].

说好的呢 2024-10-06 02:09:24

我绝不是这类事情的专家,但我遵循一个很好的

http://cagd.cs.byu.edu/~557/text/ch7.pdf (存档副本

I'm by no way an expert on this kind of thing, but I follow a nice blog that talks a lot about curves. He has link to two nice articles talking about your problem (the second link has an interactive demonstration and some source code). Other people may have much better insight into the problem but I hope this helps!

http://cagd.cs.byu.edu/~557/text/ch7.pdf (archived copy)

烟若柳尘 2024-10-06 02:09:24

我想说,最简单也可能最快的答案是将它们细分为非常小的线,并找到曲线相交的点(如果它们确实相交)。

public static void towardsCubic(double[] xy, double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, double t) {
    double x, y;
    x = (1 - t) * (1 - t) * (1 - t) * x0 + 3 * (1 - t) * (1 - t) * t * x1 + 3 * (1 - t) * t * t * x2 + t * t * t * x3;
    y = (1 - t) * (1 - t) * (1 - t) * y0 + 3 * (1 - t) * (1 - t) * t * y1 + 3 * (1 - t) * t * t * y2 + t * t * t * y3;
    xy[0] = x;
    xy[1] = y;
}

public static void towardsQuad(double[] xy, double x0, double y0, double x1, double y1, double x2, double y2, double t) {
    double x, y;
    x = (1 - t) * (1 - t) * x0 + 2 * (1 - t) * t * x1 + t * t * x2;
    y = (1 - t) * (1 - t) * y0 + 2 * (1 - t) * t * y1 + t * t * y2;
    xy[0] = x;
    xy[1] = y;
}

显然,蛮力答案很糟糕,请参阅 bo{4} 的答案,并且有很多线性几何和碰撞检测实际上会有很大帮助。


选择曲线所需的切片数。 100 之类的应该就很好了。

我们获取所有段并根据它们具有的 y 最大值对它们进行排序。然后,我们在列表中添加第二个标志,以获取该段的 y 最小值。

我们保留一个活动边的列表。

我们迭代 y 排序的段列表,当遇到前导段时,我们将其添加到活动列表中。当我们达到小 y 标记值时,我们会从活动列表中删除该段。

然后,我们可以简单地迭代整个段集,相当于一条扫描线,当我们简单地迭代列表时,单调增加 y 。我们迭代排序列表中的值,这通常会删除一个段并添加一个新段(或者对于拆分和合并节点,添加两个段或删除两个段)。从而保留相关细分市场的活跃列表。

当我们将新的活动线段添加到活动线段列表中时,我们运行快速失败相交检查,仅针对该线段和当前活动线段。

因此,当我们迭代曲线的采样线段时,我们始终准确地知道哪些线段是相关的。我们知道这些片段在 y 坐标上有重叠。我们可以快速失败任何与 x 坐标不重叠的新段。在极少数情况下,它们在 x 坐标上重叠,然后我们检查这些线段是否相交。

这可能会减少线路交叉点检查的数量,基本上减少交叉点的数量。

foreach(segment in sortedSegmentList) {
    if (segment.isLeading()) {
        checkAgainstActives(segment);
        actives.add(segment);
    }
    else actives.remove(segment)
}

checkAgainstActive() 将简单地检查该段和活动列表中的任何段是否与其 x 坐标重叠,如果重叠,则对它们运行线相交检查,并采取适当的操作。


另请注意,这适用于任何数量的曲线、任何类型的曲线、任何顺序的曲线、任何混合形式。如果我们迭代整个段列表,它将找到每个交叉点。它将找到贝塞尔曲线与圆相交的每个点或十几个二次贝塞尔曲线彼此(或自身)的每个交点,并且所有这些都在同一瞬间完成。

对于细分算法,经常引用第7章文档注释:

“一旦一对曲线被足够细分,它们都可以通过线段近似到公差范围内”,

我们实际上可以跳过中间人。我们可以足够快地完成此操作,以便简单地将线段与曲线的误差量进行比较。最后,这就是典型答案的作用。


其次,请注意,这里碰撞检测的大部分速度增加,即基于段的最高 y 排序的有序列表添加到活动列表,以及从活动列表中删除最低 y,同样可以完成直接为贝塞尔曲线的船体多边形。我们的线段只是一个 2 阶多边形,但我们可以简单地处理任意数量的点,并以我们正在处理的曲线的任何顺序检查所有控制点的边界框。因此,我们将拥有一个活动外壳多边形点列表,而不是活动线段列表。我们可以简单地使用 De Casteljau 的算法来分割曲线,从而将这些曲线采样为细分曲线而不是线段。因此,我们不会得到 2 点,而是得到 4 点或 7 点或诸如此类的东西,并且运行相同的程序很容易快速失败。

在最大 y 处添加相关的点组,在最小 y 处将其删除,然后仅比较活动列表。这样我们就可以快速、更好的实现贝塞尔细分算法。通过简单地找到边界框重叠,然后细分那些重叠的曲线,并删除那些不重叠的曲线。同样,我们可以制作任意数量的曲线,甚至是从上一次迭代中的曲线细分出来的曲线。就像我们的线段近似一样,可以非常快速地求解数百条不同曲线(甚至不同阶)之间的每个交点位置。只需检查所有曲线以查看边界框是否重叠,如果重叠,我们将它们细分,直到我们的曲线足够小或者我们用完它们。

I would say that the easiest and likely fastest answer is to subdivide them into very small lines and find the points where the curves intersect, if they actually do.

public static void towardsCubic(double[] xy, double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, double t) {
    double x, y;
    x = (1 - t) * (1 - t) * (1 - t) * x0 + 3 * (1 - t) * (1 - t) * t * x1 + 3 * (1 - t) * t * t * x2 + t * t * t * x3;
    y = (1 - t) * (1 - t) * (1 - t) * y0 + 3 * (1 - t) * (1 - t) * t * y1 + 3 * (1 - t) * t * t * y2 + t * t * t * y3;
    xy[0] = x;
    xy[1] = y;
}

public static void towardsQuad(double[] xy, double x0, double y0, double x1, double y1, double x2, double y2, double t) {
    double x, y;
    x = (1 - t) * (1 - t) * x0 + 2 * (1 - t) * t * x1 + t * t * x2;
    y = (1 - t) * (1 - t) * y0 + 2 * (1 - t) * t * y1 + t * t * y2;
    xy[0] = x;
    xy[1] = y;
}

Obviously the brute force answer is bad See bo{4}'s answer, and there's a lot of linear geometry and collision detection that will actually help quite a lot.


Pick the number of slices you want for the curves. Something like 100 should be great.

We take all the segments and sort them based on the largest value of y they have. We then, add a second flag in the list for the smallest value of y for that segment.

We keep a list of active edges.

We iterate through the y-sorted list of segment, when we encounter a leading segment we add it to the active list. When we hit the small-y flagged value, we remove that segment from the active list.

We then can simply iterate through the entire set of segments with what amounts to a scan line, increasing our y monotonically as we simply iterate the list. We iterate through the values in our sorted list, which will typically remove one segment and add a new segment (or for split and merge nodes, add two segments or remove two segments). Thereby keeping an active list of relevant segments.

We run the fast fail intersect check as we add a new active segment to the list of active segments, against only that segment and and the currently active segments.

So at all times we know exactly which line segments are relevant, as we iterate through the sampled segments of our curves. We know these segments share overlap in the y-coords. We can fast fail any new segment that does not overlap with regard to its x-coords. In the rare case that they overlap in the x-coords, we then check if these segments intersect.

This will likely reduce the number of line intersection checks to basically the number of intersections.

foreach(segment in sortedSegmentList) {
    if (segment.isLeading()) {
        checkAgainstActives(segment);
        actives.add(segment);
    }
    else actives.remove(segment)
}

checkAgainstActive() would simply check if this segment and any segment in the active list overlap their x-coords, if they do, then run the line intersect check on them, and take the appropriate action.


Also note, this will work for any number of curves, any type of curves, any order of curves, in any mixture. And if we iterate through the entire list of segments it will find every intersection. It will find every point a Bezier intersects a circle or every intersection that a dozen quadratic Bezier curves have with each other (or themselves), and all in the same split second.

The oft cited Chapter 7 document notes, for the subdivision algorithm:

"Once a pair of curves has been subdivided enough that they can each be approximated by a line segment to within a tolerance"

We can literally just skip the middleman. We can do this fast enough so to simply compare line segments with a tolerable amount of error from the curve. In the end, that's what the typical answer does.


Secondly, note, that the bulk of the speed increase for the collision detection here, namely the ordered list of segments sorted based on their highest y to add to the active list, and lowest y to remove from the active list, can likewise be done for the hull polygons of the Bezier curve directly. Our line segment is simply a polygon of order 2, but we could do any number of points just as trivially, and checking the bounding box of all the control points in whatever order of curve we're dealing with. So rather than a list of active segments, we would have a list of active hull polygons points. We could simply use De Casteljau's algorithm to split the curves, thereby sampling as these as subdivided curves rather than line segments. So rather than 2 points we'd have 4 or 7 or whatnot, and run the same routine being quite apt towards fast failing.

Adding the relevant group of points at max y, removing it at min y, and comparing only the active list. We can thus quickly and better implement the Bezier subdivision algorithm. By simply finding bounding box overlap, and then subdividing those curves which overlapped, and removing those that did not. As, again, we can do any number of curves, even ones subdivided from curves in the previous iteration. And like our segment approximation quickly solve for every intersection position between hundreds of different curves (even of different orders) very quickly. Just check all curves to see if the bounding boxes overlap, if they do, we subdivide those, until our curves are small enough or we ran out of them.

记忆で 2024-10-06 02:09:24

是的,我知道,这个帖子被接受并关闭了很长一段时间,但是......

首先,我要感谢你,zneak,给我灵感。您的努力可以找到正确的方法。

其次,我对所接受的解决方案不太满意。这种类型用在我最喜欢的免费软件 IPE 中,它的 bugzilla 有很多关于交叉点问题的准确性和可靠性低的抱怨,我的也是其中之一。

缺少的技巧允许以zneak提出的方式解决问题:将一条曲线缩短一个因子k< em>>0,则西尔维斯特矩阵的行列式将等于0。显然,如果缩短的曲线会相交,那么原始曲线也会相交。现在,任务变为搜索 k 因子的合适值。这导致了求解 9 次单变量多项式的问题。该多项式的实根和正根负责潜在交点。 (这不足为奇,两条三次贝塞尔曲线最多可以相交 9 次。)执行最终选择以仅查找那些提供 0< 的 k 因子;=t<=1 对于两条曲线。

现在是 Maxima 代码,其中起点是由 zneak 提供的 8 个点:

p0x:1; p0y:1;
p1x:2; p1y:4;
p2x:3; p2y:4;
p3x:4; p3y:3;

q0x:3; q0y:5;
q1x:3; q1y:6;
q2x:0; q2y:1;
q3x:3; q3y:1;

c0x:p0x;
c0y:p0y;
c1x:3*(p1x-p0x);
c1y:3*(p1y-p0y);
c2x:3*(p2x+p0x)-6*p1x;
c2y:3*(p2y+p0y)-6*p1y;
c3x:3*(p1x-p2x)+p3x-p0x;
c3y:3*(p1y-p2y)+p3y-p0y;

d0x:q0x;
d0y:q0y;
d1x:3*(q1x-q0x);
d1y:3*(q1y-q0y);
d2x:3*(q2x+q0x)-6*q1x;
d2y:3*(q2y+q0y)-6*q1y;
d3x:3*(q1x-q2x)+q3x-q0x;
d3y:3*(q1y-q2y)+q3y-q0y;

x:c0x-d0x + (c1x-d1x*k)*t+ (c2x-d2x*k^2)*t^2+ (c3x-d3x*k^3)*t^3;
y:c0y-d0y + (c1y-d1y*k)*t+ (c2y-d2y*k^2)*t^2+ (c3y-d3y*k^3)*t^3;

z:resultant(x,y,t);

此时,Maxima 回答:

(%o35)−2*(1004*k^9−5049*k^8+5940*k^7−1689*k^6+10584*k^5−8235*k^4−2307*k^3+1026*k^2+108*k+76)

让 Maxima 求解这个方程:

rr: float( realroots(z,1e-20))  

答案是:

(%o36) [k=−0.40256438624399,k=0.43261490325108,k=0.84718739982868,k=2.643321910825066,k=2.71772491293651]

现在是选择正确值的代码k

for item in rr do ( 
  evk:ev(k,item),
  if evk>0 then (  
    /*print("k=",evk),*/ 
    xx:ev(x,item),  rx:float( realroots(xx,1e-20)),/*print("x(t)=",xx,"   roots: ",rx),*/
    yy:ev(y,item),  ry:float( realroots(yy,1e-20)),/*print("y(t)=",yy,"   roots: ",ry),*/
    for it1 in rx do (  t1:ev(t,it1),
    for it2 in ry do (  t2:ev(t,it2),
       dt:abs(t1-t2),
       if dt<1e-10 then (
         /*print("Common root=",t1,"  delta t=",dt),*/
         if (t1>0) and (t1<=1) then ( t2:t1*evk,
         if (t2>0) and (t2<=1) then (                           
                 x1:c0x + c1x*t1+ c2x*t1^2+ c3x*t1^3,
                 y1:c0y + c1y*t1+ c2y*t1^2+ c3y*t1^3,
                 print("Intersection point:     x=",x1, "      y=",y1)
)))))/*,disp ("-----")*/
));

马克西玛的回答:

"Intersection point:     x="1.693201254437358"      y="2.62375005067273
(%o37) done

不过,不仅仅是蜂蜜。 所提出的方法不可用

  • 如果: P0=Q0,或更一般地,如果 P0 位于第二条曲线(或其延伸)上,则 。人们可以尝试交换曲线。
  • 这是一种极其罕见的情况,即两条曲线都属于一个 K 族(例如,它们的无限延伸相同)。
  • 留意 (sqr(c3x)+sqr(c3y))=0(sqr(d3x)+sqr(3y))=0 情况,这里是二次假装是三次贝塞尔曲线。

有人可能会问,为什么缩短只执行一次。这已经足够了,因为逆反定律是被发现的en passant,但这是另一个故事了。

Yes, I know, this thread is accepted and closed for a long time, but...

First, I'd like to thank you, zneak, for an inspiration. Your effort allows to find the right way.

Second, I was not quite happy with the accepted solution. This kind is used in my favorite freeware IPE, and its bugzilla is plenty of complains for a low accuracy and reliability about an intersection issue, my among them.

The missing trick which allows to solve the problem in a manner proposed by zneak : It is enough to shorten one of curves by a factor k>0, then the determinant of Sylvester matrix will be equal to zero. It is obvious, if a shortened curve will intersect, then original will too. Now, the task is changed into the search for a suitable value for k factor. This has led to the problem of solving an univariate polynomial of 9 degree. A real and positive roots of this polynomial are responsible for potential points of intersection. (This shouldn't be a surprise, two cubic Bezier curves can intersect up to 9 times.) The final selection is performed to find only those k factors, which provide 0<=t<=1 for both curves.

Now the Maxima code, where the starting point is set of 8 points provided by zneak :

p0x:1; p0y:1;
p1x:2; p1y:4;
p2x:3; p2y:4;
p3x:4; p3y:3;

q0x:3; q0y:5;
q1x:3; q1y:6;
q2x:0; q2y:1;
q3x:3; q3y:1;

c0x:p0x;
c0y:p0y;
c1x:3*(p1x-p0x);
c1y:3*(p1y-p0y);
c2x:3*(p2x+p0x)-6*p1x;
c2y:3*(p2y+p0y)-6*p1y;
c3x:3*(p1x-p2x)+p3x-p0x;
c3y:3*(p1y-p2y)+p3y-p0y;

d0x:q0x;
d0y:q0y;
d1x:3*(q1x-q0x);
d1y:3*(q1y-q0y);
d2x:3*(q2x+q0x)-6*q1x;
d2y:3*(q2y+q0y)-6*q1y;
d3x:3*(q1x-q2x)+q3x-q0x;
d3y:3*(q1y-q2y)+q3y-q0y;

x:c0x-d0x + (c1x-d1x*k)*t+ (c2x-d2x*k^2)*t^2+ (c3x-d3x*k^3)*t^3;
y:c0y-d0y + (c1y-d1y*k)*t+ (c2y-d2y*k^2)*t^2+ (c3y-d3y*k^3)*t^3;

z:resultant(x,y,t);

At this point, Maxima answered:

(%o35)−2*(1004*k^9−5049*k^8+5940*k^7−1689*k^6+10584*k^5−8235*k^4−2307*k^3+1026*k^2+108*k+76)

Let Maxima solve this equation:

rr: float( realroots(z,1e-20))  

The answer is:

(%o36) [k=−0.40256438624399,k=0.43261490325108,k=0.84718739982868,k=2.643321910825066,k=2.71772491293651]

Now the code to select a right value of k:

for item in rr do ( 
  evk:ev(k,item),
  if evk>0 then (  
    /*print("k=",evk),*/ 
    xx:ev(x,item),  rx:float( realroots(xx,1e-20)),/*print("x(t)=",xx,"   roots: ",rx),*/
    yy:ev(y,item),  ry:float( realroots(yy,1e-20)),/*print("y(t)=",yy,"   roots: ",ry),*/
    for it1 in rx do (  t1:ev(t,it1),
    for it2 in ry do (  t2:ev(t,it2),
       dt:abs(t1-t2),
       if dt<1e-10 then (
         /*print("Common root=",t1,"  delta t=",dt),*/
         if (t1>0) and (t1<=1) then ( t2:t1*evk,
         if (t2>0) and (t2<=1) then (                           
                 x1:c0x + c1x*t1+ c2x*t1^2+ c3x*t1^3,
                 y1:c0y + c1y*t1+ c2y*t1^2+ c3y*t1^3,
                 print("Intersection point:     x=",x1, "      y=",y1)
)))))/*,disp ("-----")*/
));

Maxima's answer:

"Intersection point:     x="1.693201254437358"      y="2.62375005067273
(%o37) done

Theres not only a honey, though. The presented method is unusable, if:

  • P0=Q0, or more generally, if P0 lies on the second curve (or its extension). One can try to swap the curves.
  • an extremely rare cases, when both curves belong to one K-family (eg. their infinite extensions are the same).
  • keep an eyes on (sqr(c3x)+sqr(c3y))=0 or (sqr(d3x)+sqr(3y))=0 cases, here a quadratic are pretending to be a cubic Bezier curves.

One can ask, why a shortening is performed only once. It's enough because of reverse-inverse law, which was discovered en passant, but this is an another story.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文