如何通过点来使一条线变成曲线

11

我正在寻找一种方法让一条线条曲线通过多个点。最好使用3个点,尽管我考虑过为了给予线条进入某个点的角度上下文,可能需要更多点来给曲线提供上下文。

通常情况下,有起始点 P1,控制点 P2 和结束点 P3,线条应该从 P1 曲线到 P2,然后从 P2 曲线到 P3。

事实上,这里有一个完美的例子,展示了我想实现的效果:

Irwin Hall Spline

如果我能做到这一点,我将非常感激!

在 Java 中,我已经尝试过使用诸如 QuadCurve2D.DoubleCubicCurve2D.Double 以及 Path2D.Double(使用 Path2D.Double 的 curveTo)等方法,但都没有成功——绘制的曲线与指定的控制点相差甚远。

这里是我迄今为止尝试过的方法的图像:

enter image description here

这是我用来生成图片中的点和曲线的代码:

    Graphics2D g = (Graphics2D) window.getGraphics();
    g.setColor(Color.blue);
    int d = 4;

    // P0
    int x0 = window.getWidth()/8;
    int y0 = 250;
    g.drawString("P0", x0, y0 + 4*d);
    g.fillRect(x0, y0, d, d);

    // P1
    int x1 = (window.getWidth()/7)*2;
    int y1 = 235;
    g.drawString("P1", x1, y1 + 4*d);
    g.fillRect(x1, y1, d, d);

    // P2
    int x2 = (window.getWidth()/2);
    int y2 = 200;
    g.drawString("P2", x2, y2 - 2*d);
    g.fillRect(x2, y2, d, d);

    // P3
    int x3 = (window.getWidth()/7)*5;
    int y3 = 235;
    g.drawString("P3", x3, y3 + 4*d);
    g.fillRect(x3, y3, d, d);

            // P4
    int x4 = (window.getWidth()/8)*7;
    int y4 = 250;
    g.drawString("P4", x4, y4 + 4*d);
    g.fillRect(x4, y4, d, d);

    g.setColor(Color.cyan);
    QuadCurve2D quadCurve = new QuadCurve2D.Double(x0, y0, x2, y2, x4, y4);
    g.draw(quadCurve);


    g.setColor(Color.YELLOW);
    CubicCurve2D.Double cubicCurve = new CubicCurve2D.Double((double)x0, (double)y0, 
                                                             (double)x1, (double)y1, 
                                                             (double)x2, (double)y2, 
                                                             (double)x4, (double)y4);
    g.draw(cubicCurve);


    g.setColor(Color.red);      
    Path2D.Double path1 = new Path2D.Double();
    path1.moveTo(x1, y1);
    path1.curveTo(x0, y0, x2, y2, x4, y4);
    g.draw(path1);
我的想法是希望一条曲线能够穿过这些点,这样可以使我写的折线图的顶点之间的过渡更加平滑。在任何人提到JFree Chart之前,请注意它不是我的选择。我知道有不同类型的曲线和样条线可用,但我并没有很好地理解它们的工作原理或如何实现适合我的需求。非常感谢您提供的任何帮助 - 提前致谢。

让我澄清一下,您是在询问是否能够通过一系列点绘制曲线吗? - Joban
是的,因此请考虑Irwin-Hall样条上的每个点都是数组中的一个元素。 - James C
3个回答

11

我认为你没有理解控制点的概念。控制点通常不在路径本身上。相反,它们控制着路径在两个点之间弯曲的方式。请查看样条教程以获取完整的详细信息。

现在来谈谈手头的问题,你有曲线上的点但没有实际的控制点。有一些技巧,比如基数样条,可以派生出控制点,然后将其传递给你提到的其中一个曲线绘制API。你可能想要使用Path2D.Double选项,这样可以平滑地连接各个单独的曲线。

因此,对于从P1到P2到P3的绘制,你可以使用:

Path2D.Double path1 = new Path2D.Double();
path1.moveTo(x1, y1);
path1.curveTo(x0, y0, x2, y2, x4, y4);
g.draw(path1);

你想要什么

Path2D.Double path1 = new Path2D.Double();
path1.moveTo(x1, y1);
path1.curveTo(cx1a, cy1a, cx1b, cy1b, x2, y2);
path1.curveTo(cx2a, cy2a, cx2b, cy2b, x3, y3);
g.draw(path1);

这里的cxcy坐标是您导出的控制点,每个三次样条线段需要两个控制点。

cx1a = x1 + (x2 - x1) / 3;
cy1a = y1 + (y2 - y1) / 3;
cx1b = x2 - (x3 - x1) / 3;
cy1b = y2 - (y3 - y1) / 3;
cx2a = x2 + (x3 - x1) / 3;
cy2a = y2 + (y3 - y1) / 3;
cx2b = x3 - (x3 - x2) / 3;
cy2b = y3 - (y3 - y2) / 3;

这里的规律是对于内部点(仅在本例中是P2),其前后控制点(c1b和c2a)将偏移其前后两点之间连线的斜率(即P1和P3)。对于边缘点,控制点则基于该点与最近的下一个点之间的斜率。

如果您拥有特定领域的信息,可以选择不同的控制点。例如,您可能希望强制端点处的斜率为0。


很确定cx1b和cy1b不应该重复?它们不应该是cx1a cy1a cx1b cy1b吗? - James C
如果不麻烦的话,你能解释一下为什么选择这样的x和y值来构建控制点吗?为什么除了最后一对控制点之外,其他所有控制点都使用x1、y1?而且在第二对控制点cx1b和cy1b中,它们似乎取决于曲线靠近后续点的位置,但在最后一对中,显然无法再进行额外的步骤,因为你正在接近最后一个点。所以我想你可能会使用未来点x3、y3来确定曲线接近第二个点时的角度,因为它将顺利地延续到未来的点上。 - James C
好的。我在我的回答中添加了有关控制点公式的更多信息。 - xan

2

Catmull-Rom样条与Irwin-Hall样条在曲线绘制方式上有所不同。请查看此资源并绘制一些点,http://blog.ivank.net/interpolation-with-cubic-splines.html。 - Joban
谢谢你提供的链接,Joban。然而,如果你考虑我在你提供的链接上创建的这个例子,你会发现曲线(至少在我的情况下)会错误地给人留下最后两个点之间存在更大值的印象。Swim提供的Catmull-Rom示例是可以接受的,但实现不太清晰 - 你有什么想法,Swim? - James C
好的,所以你想要一个Catmull-Rom样条曲线,嗯,那可能会更容易一些。 - Joban

2
基本上你要求的是三次样条插值,我在网上找到了这个程序Interp2.java。它实际上包括了多项式样条和三次样条。
不幸的是,它只是一个小应用程序而不是一个实际的类,但你仍然可以查看代码并学习他们是如何做到的。这总是一件好事。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接