如何绘制通过三个点的曲线?

时间:2022-09-15 20:50:46

I'm trying to create a curve that passes through three given points in Java (I'm drawing the curves through a class that extends JPanel). How can I make it?

我正在尝试创建一条曲线,该曲线通过Java中的三个给定点(我通过扩展JPanel的类绘制曲线)。我该怎么做?

如何绘制通过三个点的曲线?

5 个解决方案

#1


You should look into something like Catmull-Rom splines, which are basically curves that pass through a number of control points (in your case your three points).

你应该研究类似Catmull-Rom样条的东西,它们基本上是通过许多控制点的曲线(在你的情况下是你的三个点)。

Here is an example I found after a quick google: http://www.mvps.org/directx/articles/catmull/

以下是我在快速谷歌发现后的一个例子:http://www.mvps.org/directx/articles/catmull/

Hope this helps :)

希望这可以帮助 :)

#2


A circle will pass through three points on a plane. This page explains the geometery: http://www.mathopenref.com/const3pointcircle.html

一个圆圈将通过一个平面上的三个点。此页面解释了几何:http://www.mathopenref.com/const3pointcircle.html

#3


I just spent some time getting this working in a robust way. There's a few supporting functions, followed by the thing that creates an Arc2D out of three points of a circle. For my purposes, I have a start and end points, as well as an intermediate 'mid' point (though it doesn't actually have to be in the middle---its purpose is to tell me which arc of the circle I want).

我只是花了一些时间才能以健壮的方式工作。有一些支持函数,后面是用圆圈中的三个点创建Arc2D的东西。为了我的目的,我有一个起点和终点,以及一个中间的“中间”点(虽然它实际上不必在中间---它的目的是告诉我我想要的圆弧)。

Here are direct links to the source:

以下是源代码的直接链接:

org.six11.util.gui.shape.ShapeFactory

org.six11.util.pen.Functions

public static Pt getCircleCenter(Pt a, Pt b, Pt c) {
  double ax = a.getX();
  double ay = a.getY();
  double bx = b.getX();
  double by = b.getY();
  double cx = c.getX();
  double cy = c.getY();

  double A = bx - ax;
  double B = by - ay;
  double C = cx - ax;
  double D = cy - ay;

  double E = A * (ax + bx) + B * (ay + by);
  double F = C * (ax + cx) + D * (ay + cy);

  double G = 2 * (A * (cy - by) - B * (cx - bx));
  if (G == 0.0)
    return null; // a, b, c must be collinear

  double px = (D * E - B * F) / G;
  double py = (A * F - C * E) / G;
  return new Pt(px, py);
}

public static double makeAnglePositive(double angleDegrees) {
  double ret = angleDegrees;
  if (angleDegrees < 0) {
    ret = 360 + angleDegrees;
  }
  return ret;
}

public static double getNearestAnglePhase(double limitDegrees, double sourceDegrees, int dir) {
  double value = sourceDegrees;
  if (dir > 0) {
    while (value < limitDegrees) {
      value += 360.0;
    }
  } else if (dir < 0) {
    while (value > limitDegrees) {
      value -= 360.0;
    }
  }
  return value;
}

public static Arc2D makeArc(Pt s, Pt mid, Pt e) {
  Pt c = Functions.getCircleCenter(s, mid, e);
  double radius = c.distance(s);

  double startAngle = Functions.makeAnglePositive(Math.toDegrees(-Math
      .atan2(s.y - c.y, s.x - c.x)));
  double midAngle = Functions.makeAnglePositive(Math.toDegrees(-Math.atan2(mid.y - c.y, mid.x
      - c.x)));
  double endAngle = Functions
      .makeAnglePositive(Math.toDegrees(-Math.atan2(e.y - c.y, e.x - c.x)));

  // Now compute the phase-adjusted angles begining from startAngle, moving positive and negative.
  double midDecreasing = Functions.getNearestAnglePhase(startAngle, midAngle, -1);
  double midIncreasing = Functions.getNearestAnglePhase(startAngle, midAngle, 1);
  double endDecreasing = Functions.getNearestAnglePhase(midDecreasing, endAngle, -1);
  double endIncreasing = Functions.getNearestAnglePhase(midIncreasing, endAngle, 1);

  // Each path from start -> mid -> end is technically, but one will wrap around the entire
  // circle, which isn't what we want. Pick the one that with the smaller angular change.
  double extent = 0;
  if (Math.abs(endDecreasing - startAngle) < Math.abs(endIncreasing - startAngle)) {
    extent = endDecreasing - startAngle;
  } else {
    extent = endIncreasing - startAngle;
  }

  return new Arc2D.Double(c.x - radius, c.y - radius, radius * 2, radius * 2, startAngle, extent,
      Arc2D.OPEN);
}

#4


try a google search on bezier splines. this may be a 2D solution, but should be extensible to 3D if you need it.

尝试在bezier样条上进行谷歌搜索。这可能是一个2D解决方案,但如果需要,应该可以扩展为3D。

basically, using the three points as parameters you can get an 2nd order polynomial that fits the three points .. AND its extensible, if you have N points you get an N-1 order polynomial that parametrically generates all the points from the 1st to the last, as you 'tune' a scalar parameter, oft denoted as 's'.

基本上,使用三个点作为参数,你可以得到一个适合三个点的二阶多项式。并且它是可扩展的,如果你有N个点,你得到一个N-1阶多项式,参数化地生成从第一个到第一个的所有点。最后,当你'调整'一个标量参数时,经常表示为's'。

edit/added:

as was pointed out (credit CapBBeard!), Beziers don't actually hit the middle points. Lagrangian interpolation does actually hit the points, but gets ugly even more quickly as the number of points grows. (something like O(n) polynomial fractions each of order N)

正如所指出的那样(信用CapBBeard!),Beziers实际上没有达到中间点。拉格朗日插值实际上确实达到了分数,但随着点数的增加变得更加丑陋。 (类似O(n)个多项式分数,每个阶数为N)

#5


use 2 curves
curveLine1 created with 3 points : P1, C1, E1.
curveLine2 created with 3 points : P2 = E1, C2, E2.
make C1, E1, C2 in 1 line.
View example here : http://i.stack.imgur.com/I901q.png

使用2曲线curveLine1创建3点:P1,C1,E1。用3个点创建的curveLine2:P2 = E1,C2,E2。使C1,E1,C2在1行。查看示例:http://i.stack.imgur.com/I901q.png

QuadCurve2D.Double curveLine1 = new QuadCurve2D.Double(30, 75, 195, 23, 280, 143);
//QuadCurve2D.Double curveLine1 = new QuadCurve2D.Double(P1.x, P1.y, C1.x, C1.y, E1.x, E1.y);
QuadCurve2D.Double curveLine2 = new QuadCurve2D.Double(280, 143, 366, 260, 466, 193);
//QuadCurve2D.Double curveLine2 = new QuadCurve2D.Double(E1.x, E1.y, C2.x, C2.y, E2.x, E2.y);
Graphics2D g2 = (Graphics2D) g;
g2.draw(curveLine1);
g2.draw(curveLine2);

#1


You should look into something like Catmull-Rom splines, which are basically curves that pass through a number of control points (in your case your three points).

你应该研究类似Catmull-Rom样条的东西,它们基本上是通过许多控制点的曲线(在你的情况下是你的三个点)。

Here is an example I found after a quick google: http://www.mvps.org/directx/articles/catmull/

以下是我在快速谷歌发现后的一个例子:http://www.mvps.org/directx/articles/catmull/

Hope this helps :)

希望这可以帮助 :)

#2


A circle will pass through three points on a plane. This page explains the geometery: http://www.mathopenref.com/const3pointcircle.html

一个圆圈将通过一个平面上的三个点。此页面解释了几何:http://www.mathopenref.com/const3pointcircle.html

#3


I just spent some time getting this working in a robust way. There's a few supporting functions, followed by the thing that creates an Arc2D out of three points of a circle. For my purposes, I have a start and end points, as well as an intermediate 'mid' point (though it doesn't actually have to be in the middle---its purpose is to tell me which arc of the circle I want).

我只是花了一些时间才能以健壮的方式工作。有一些支持函数,后面是用圆圈中的三个点创建Arc2D的东西。为了我的目的,我有一个起点和终点,以及一个中间的“中间”点(虽然它实际上不必在中间---它的目的是告诉我我想要的圆弧)。

Here are direct links to the source:

以下是源代码的直接链接:

org.six11.util.gui.shape.ShapeFactory

org.six11.util.pen.Functions

public static Pt getCircleCenter(Pt a, Pt b, Pt c) {
  double ax = a.getX();
  double ay = a.getY();
  double bx = b.getX();
  double by = b.getY();
  double cx = c.getX();
  double cy = c.getY();

  double A = bx - ax;
  double B = by - ay;
  double C = cx - ax;
  double D = cy - ay;

  double E = A * (ax + bx) + B * (ay + by);
  double F = C * (ax + cx) + D * (ay + cy);

  double G = 2 * (A * (cy - by) - B * (cx - bx));
  if (G == 0.0)
    return null; // a, b, c must be collinear

  double px = (D * E - B * F) / G;
  double py = (A * F - C * E) / G;
  return new Pt(px, py);
}

public static double makeAnglePositive(double angleDegrees) {
  double ret = angleDegrees;
  if (angleDegrees < 0) {
    ret = 360 + angleDegrees;
  }
  return ret;
}

public static double getNearestAnglePhase(double limitDegrees, double sourceDegrees, int dir) {
  double value = sourceDegrees;
  if (dir > 0) {
    while (value < limitDegrees) {
      value += 360.0;
    }
  } else if (dir < 0) {
    while (value > limitDegrees) {
      value -= 360.0;
    }
  }
  return value;
}

public static Arc2D makeArc(Pt s, Pt mid, Pt e) {
  Pt c = Functions.getCircleCenter(s, mid, e);
  double radius = c.distance(s);

  double startAngle = Functions.makeAnglePositive(Math.toDegrees(-Math
      .atan2(s.y - c.y, s.x - c.x)));
  double midAngle = Functions.makeAnglePositive(Math.toDegrees(-Math.atan2(mid.y - c.y, mid.x
      - c.x)));
  double endAngle = Functions
      .makeAnglePositive(Math.toDegrees(-Math.atan2(e.y - c.y, e.x - c.x)));

  // Now compute the phase-adjusted angles begining from startAngle, moving positive and negative.
  double midDecreasing = Functions.getNearestAnglePhase(startAngle, midAngle, -1);
  double midIncreasing = Functions.getNearestAnglePhase(startAngle, midAngle, 1);
  double endDecreasing = Functions.getNearestAnglePhase(midDecreasing, endAngle, -1);
  double endIncreasing = Functions.getNearestAnglePhase(midIncreasing, endAngle, 1);

  // Each path from start -> mid -> end is technically, but one will wrap around the entire
  // circle, which isn't what we want. Pick the one that with the smaller angular change.
  double extent = 0;
  if (Math.abs(endDecreasing - startAngle) < Math.abs(endIncreasing - startAngle)) {
    extent = endDecreasing - startAngle;
  } else {
    extent = endIncreasing - startAngle;
  }

  return new Arc2D.Double(c.x - radius, c.y - radius, radius * 2, radius * 2, startAngle, extent,
      Arc2D.OPEN);
}

#4


try a google search on bezier splines. this may be a 2D solution, but should be extensible to 3D if you need it.

尝试在bezier样条上进行谷歌搜索。这可能是一个2D解决方案,但如果需要,应该可以扩展为3D。

basically, using the three points as parameters you can get an 2nd order polynomial that fits the three points .. AND its extensible, if you have N points you get an N-1 order polynomial that parametrically generates all the points from the 1st to the last, as you 'tune' a scalar parameter, oft denoted as 's'.

基本上,使用三个点作为参数,你可以得到一个适合三个点的二阶多项式。并且它是可扩展的,如果你有N个点,你得到一个N-1阶多项式,参数化地生成从第一个到第一个的所有点。最后,当你'调整'一个标量参数时,经常表示为's'。

edit/added:

as was pointed out (credit CapBBeard!), Beziers don't actually hit the middle points. Lagrangian interpolation does actually hit the points, but gets ugly even more quickly as the number of points grows. (something like O(n) polynomial fractions each of order N)

正如所指出的那样(信用CapBBeard!),Beziers实际上没有达到中间点。拉格朗日插值实际上确实达到了分数,但随着点数的增加变得更加丑陋。 (类似O(n)个多项式分数,每个阶数为N)

#5


use 2 curves
curveLine1 created with 3 points : P1, C1, E1.
curveLine2 created with 3 points : P2 = E1, C2, E2.
make C1, E1, C2 in 1 line.
View example here : http://i.stack.imgur.com/I901q.png

使用2曲线curveLine1创建3点:P1,C1,E1。用3个点创建的curveLine2:P2 = E1,C2,E2。使C1,E1,C2在1行。查看示例:http://i.stack.imgur.com/I901q.png

QuadCurve2D.Double curveLine1 = new QuadCurve2D.Double(30, 75, 195, 23, 280, 143);
//QuadCurve2D.Double curveLine1 = new QuadCurve2D.Double(P1.x, P1.y, C1.x, C1.y, E1.x, E1.y);
QuadCurve2D.Double curveLine2 = new QuadCurve2D.Double(280, 143, 366, 260, 466, 193);
//QuadCurve2D.Double curveLine2 = new QuadCurve2D.Double(E1.x, E1.y, C2.x, C2.y, E2.x, E2.y);
Graphics2D g2 = (Graphics2D) g;
g2.draw(curveLine1);
g2.draw(curveLine2);