2013-05-21 79 views
0

我无法绘制由3个点描述的最小弧:弧中心,一个“锚定”终点,以及第二个点通过确定半径来给出弧的另一端。我使用余弦定律来确定弧的长度,并尝试使用atan作为起始角度,但弧的起始位置已关闭。2点之间的Java绘制弧

我设法电弧锁定在定位点(X1,Y1),当它在第二象限,但只会工作,当它在象限2

解决方案,我可以看到所有有一堆if语句来确定2点相对于彼此的位置,但我很好奇,如果我忽略了简单的事情。任何帮助将不胜感激。

SSCCE:

import javax.swing.JComponent; 
import javax.swing.JFrame; 

import java.awt.event.MouseEvent; 
import java.awt.event.MouseListener; 
import java.awt.geom.*; 
import java.awt.*; 
import java.util.*; 

class Canvas extends JComponent { 
    float circleX, circleY, x1, y1, x2, y2, dx, dy, dx2, dy2, radius, radius2; 
    Random random = new Random(); 

    public Canvas() { 

     //Setup. 

     x1 = random.nextInt(250); 
     y1 = random.nextInt(250); 

     //Cant have x2 == circleX 
     while (x1 == 150 || y1 == 150) 
     { 
      x1 = random.nextInt(250); 
      y1 = random.nextInt(250); 
     } 

     circleX = 150; //circle center is always dead center. 
     circleY = 150; 


     //Radius between the 2 points must be equal. 
     dx = Math.abs(circleX-x1); 
     dy = Math.abs(circleY-y1); 

     //c^2 = a^2 + b^2 to solve for the radius 
     radius = (float) Math.sqrt((float)Math.pow(dx, 2) + (float)Math.pow(dy, 2)); 

     //2nd random point 
     x2 = random.nextInt(250); 
     y2 = random.nextInt(250); 

     //I need to push it out to radius length, because the radius is equal for both points. 
     dx2 = Math.abs(circleX-x2); 
     dy2 = Math.abs(circleY-y2); 
     radius2 = (float) Math.sqrt((float)Math.pow(dx2, 2) + (float)Math.pow(dy2, 2)); 

     dx2 *= radius/radius2; 
     dy2 *= radius/radius2; 

     y2 = circleY+dy2; 
     x2 = circleX+dx2; 
     //Radius now equal for both points. 
    } 

    public void paintComponent(Graphics g2) { 
     Graphics2D g = (Graphics2D) g2; 
     g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
       RenderingHints.VALUE_ANTIALIAS_ON); 
     g.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_BUTT, 
       BasicStroke.JOIN_BEVEL)); 

     Arc2D.Float centerPoint = new Arc2D.Float(150-2,150-2,4,4, 0, 360, Arc2D.OPEN); 
     Arc2D.Float point1 = new Arc2D.Float(x1-2, y1-2, 4, 4, 0, 360, Arc2D.OPEN); 
     Arc2D.Float point2 = new Arc2D.Float(x2-2, y2-2, 4, 4, 0, 360, Arc2D.OPEN); 

     //3 points drawn in black 
     g.setColor(Color.BLACK); 
     g.draw(centerPoint); 
     g.draw(point1); 
     g.draw(point2); 

     float start = 0; 
     float distance; 

     //Form a right triangle to find the length of the hypotenuse. 
     distance = (float) Math.sqrt(Math.pow(Math.abs(x2-x1),2) + Math.pow(Math.abs(y2-y1), 2)); 

     //Law of cosines to determine the internal angle between the 2 points. 
     distance = (float) (Math.acos(((radius*radius) + (radius*radius) - (distance*distance))/(2*radius*radius)) * 180/Math.PI); 

     float deltaY = circleY - y1; 
     float deltaX = circleX - x1; 

     float deltaY2 = circleY - y2; 
     float deltaX2 = circleX - x2; 

     float angleInDegrees = (float) ((float) Math.atan((float) (deltaY/deltaX)) * 180/Math.PI); 
     float angleInDegrees2 = (float) ((float) Math.atan((float) (deltaY2/deltaX2)) * 180/Math.PI); 

     start = angleInDegrees; 

     //Q2 works. 
     if (x1 < circleX) 
     { 
      if (y1 < circleY) 
      { 
       start*=-1; 
       start+=180; 
      } else if (y2 > circleX) { 
       start+=180; 
       start+=distance; 
      } 
     } 

     //System.out.println("Start: " + start); 
     //Arc drawn in blue 
     g.setColor(Color.BLUE); 
     Arc2D.Float arc = new Arc2D.Float(circleX-radius, //Center x 
              circleY-radius, //Center y Rotates around this point. 
              radius*2, 
              radius*2, 
              start, //start degree 
              distance, //distance to travel 
              Arc2D.OPEN); //Type of arc. 
     g.draw(arc); 
    } 
} 

public class Angle implements MouseListener { 

    Canvas view; 
    JFrame window; 

    public Angle() { 
     window = new JFrame(); 
     view = new Canvas(); 
     view.addMouseListener(this); 
     window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     window.setBounds(30, 30, 400, 400); 
     window.getContentPane().add(view); 
     window.setVisible(true); 
    } 

    public static void main(String[] a) { 
     new Angle(); 
    } 

    @Override 
    public void mouseClicked(MouseEvent arg0) { 
     window.getContentPane().remove(view); 
     view = new Canvas(); 
     window.getContentPane().add(view); 
     view.addMouseListener(this); 
     view.revalidate(); 
     view.repaint(); 
    } 

    @Override 
    public void mouseEntered(MouseEvent arg0) { 
     // TODO Auto-generated method stub 

    } 

    @Override 
    public void mouseExited(MouseEvent arg0) { 
     // TODO Auto-generated method stub 

    } 

    @Override 
    public void mousePressed(MouseEvent arg0) { 
     // TODO Auto-generated method stub 

    } 

    @Override 
    public void mouseReleased(MouseEvent arg0) { 
     // TODO Auto-generated method stub 

    } 
} 
+0

我不明白你的问题;你问问圆弧长度是圆的圆周上的两个点,给定一个任意的中心点? – cabbagery

+0

号我问如何在2个点之间绘制最小的弧,给定一个圆的中心和2个点在圆上。我真的在寻找起点 - 从哪里开始绘制弧线,以及是否应该切换弧线的方向(顺时针/逆时针)。 –

+1

我仍然在努力 - 乍一看看起来非常简单,但在进一步的评论中显得非常复杂,但我无法改变最终实际上相当简单的感觉。无论如何,我已经确定了一些会让事情变得棘手的场景。如果我能以可普遍的方式解决它们,我可能还没有一个可行的答案。 – cabbagery

回答

1

也许这会有所帮助。它通过点击和拖动测试来设置两点而不是随机数。这远远超过了你目前尝试使用的其他解决方案。

注:

  • Math.atan2()是在这样的问题的朋友。
  • 小助手函数使得更容易推理您的代码。
  • 最佳做法是仅使用实例变量独立值并计算局部变量中的相关值。
  • 我的代码修复了一些Swing使用问题,例如从主线程调用Swing函数。

代码如下:

import java.awt.*; 
import java.awt.event.*; 
import java.awt.geom.*; 
import javax.swing.*; 
import javax.swing.event.MouseInputAdapter; 

class TestCanvas extends JComponent { 

    float x0 = 150f, y0 = 150f; // Arc center. Subscript 0 used for center throughout. 
    float xa = 200f, ya = 150f; // Arc anchor point. Subscript a for anchor. 
    float xd = 150f, yd = 50f; // Point determining arc angle. Subscript d for determiner. 

    // Return the distance from any point to the arc center. 
    float dist0(float x, float y) { 
     return (float)Math.sqrt(sqr(x - x0) + sqr(y - y0)); 
    } 

    // Return polar angle of any point relative to arc center. 
    float angle0(float x, float y) { 
     return (float)Math.toDegrees(Math.atan2(y0 - y, x - x0)); 
    } 

    @Override 
    protected void paintComponent(Graphics g0) { 
     Graphics2D g = (Graphics2D) g0; 

     // Can always draw the center point. 
     dot(g, x0, y0); 

     // Get radii of anchor and det point. 
     float ra = dist0(xa, ya); 
     float rd = dist0(xd, yd); 

     // If either is zero there's nothing else to draw. 
     if (ra == 0 || rd == 0) { return; } 

     // Get the angles from center to points. 
     float aa = angle0(xa, ya); 
     float ad = angle0(xd, yd); // (xb, yb) would work fine, too. 

     // Draw the arc and other dots. 
     g.draw(new Arc2D.Float(x0 - ra, y0 - ra, // box upper left 
       2 * ra, 2 * ra,     // box width and height 
       aa, angleDiff(aa, ad),   // angle start, extent 
       Arc2D.OPEN)); 
     dot(g, xa, ya); 

     // Use similar triangles to get the second dot location. 
     float xb = x0 + (xd - x0) * ra/rd; 
     float yb = y0 + (yd - y0) * ra/rd; 
     dot(g, xb, yb); 
    } 

    // Some helper functions. 

    // Draw a small dot with the current color. 
    static void dot(Graphics2D g, float x, float y) { 
     final int rad = 2; 
     g.fill(new Ellipse2D.Float(x - rad, y - rad, 2 * rad, 2 * rad)); 
    } 

    // Return the square of a float. 
    static float sqr(float x) { return x * x; } 

    // Find the angular difference between a and b, -180 <= diff < 180. 
    static float angleDiff(float a, float b) { 
     float d = b - a; 
     while (d >= 180f) { d -= 360f; } 
     while (d < -180f) { d += 360f; } 
     return d; 
    } 

    // Construct a test canvas with mouse handling. 
    TestCanvas() { 
     addMouseListener(mouseListener); 
     addMouseMotionListener(mouseListener); 
    } 

    // Listener changes arc parameters with click and drag. 
    MouseInputAdapter mouseListener = new MouseInputAdapter() { 
     boolean mouseDown = false; // Is left mouse button down? 

     @Override 
     public void mousePressed(MouseEvent e) { 
      if (e.getButton() == MouseEvent.BUTTON1) { 
       mouseDown = true; 
       xa = xd = e.getX(); 
       ya = yd = e.getY(); 
       repaint(); 
      } 
     } 

     @Override 
     public void mouseReleased(MouseEvent e) { 
      if (e.getButton() == MouseEvent.BUTTON1) { 
       mouseDown = false; 
      } 
     } 

     @Override 
     public void mouseDragged(MouseEvent e) { 
      if (mouseDown) { 
       xd = e.getX(); 
       yd = e.getY(); 
       repaint(); 
      } 
     } 
    }; 
} 

public class Test extends JFrame { 

    public Test() { 
     setSize(400, 400); 
     setLocationRelativeTo(null); 
     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     getContentPane().add(new TestCanvas()); 
    } 

    public static void main(String[] args) { 
     // Swing code must run in the UI thread, so 
     // must invoke setVisible rather than just calling it. 
     SwingUtilities.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       new Test().setVisible(true); 
      } 
     }); 
    } 
} 
+0

这正是我正在寻找的。我从中得到的是我的arcTan计算不正确。我的程序现在效果很好。非常感谢你。 –

1
package curve; 

import java.awt.BasicStroke; 
import java.awt.Color; 
import java.awt.Graphics2D; 
import java.awt.RenderingHints; 
import java.awt.geom.Ellipse2D; 
import java.awt.geom.Line2D; 
import java.awt.geom.Rectangle2D; 
import java.awt.image.BufferedImage; 
import java.io.File; 
import java.io.IOException; 
import java.util.ArrayList; 
import java.util.List; 
import javax.imageio.ImageIO; 

public class Main 
{ 

    /** 
    * @param args the command line arguments 
    */ 
    public static void main(String[] args) throws IOException 
    { 
     PointF pFrom = new PointF(-10f, 30.0f); 
     PointF pTo = new PointF(-100f, 0.0f); 
     List<PointF> points = generateCurve(pFrom, pTo, 100f, 7f, true, true); 

     System.out.println(points); 

     // Calculate the bounds of the curve 
     Rectangle2D.Float bounds = new Rectangle2D.Float(points.get(0).x, points.get(0).y, 0, 0); 
     for (int i = 1; i < points.size(); ++i) { 
      bounds.add(points.get(i).x, points.get(i).y); 
     } 
     bounds.add(pFrom.x, pFrom.y); 
     bounds.add(pTo.x, pTo.y); 

     BufferedImage img = new BufferedImage((int) (bounds.width - bounds.x + 50), (int) (bounds.height - bounds.y + 50), BufferedImage.TYPE_4BYTE_ABGR_PRE); 
     Graphics2D g = img.createGraphics(); 
     g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 

     g.translate(25.0f - bounds.getX(), 25.0f - bounds.getY()); 
     g.setStroke(new BasicStroke(1.0f)); 


     g.setColor(Color.DARK_GRAY); 
     g.drawLine(-1000, 0, 1000, 0); 
     g.drawLine(0, -1000, 0, 1000); 

     g.setColor(Color.RED); 
     for (int i = 0; i < points.size(); ++i) { 
      if (i > 0) { 
       Line2D.Float f = new Line2D.Float(points.get(i - 1).x, points.get(i - 1).y, points.get(i).x, points.get(i).y); 
       System.out.println("Dist : " + f.getP1().distance(f.getP2())); 
//    g.draw(f); 
      } 

      g.fill(new Ellipse2D.Float(points.get(i).x - 0.8f, points.get(i).y - 0.8f, 1.6f, 1.6f)); 

     } 
     g.setColor(Color.BLUE); 
     g.fill(new Ellipse2D.Float(pFrom.x - 1, pFrom.y - 1, 3, 3)); 
     g.fill(new Ellipse2D.Float(pTo.x - 1, pTo.y - 1, 3, 3)); 

     g.dispose(); 

     ImageIO.write(img, "PNG", new File("result.png")); 
    } 

    static class PointF 
    { 

     public float x, y; 

     public PointF(float x, float y) 
     { 
      this.x = x; 
      this.y = y; 
     } 

     @Override 
     public String toString() 
     { 
      return "(" + x + "," + y + ")"; 
     } 
    } 

    private static List<PointF> generateCurve(PointF pFrom, PointF pTo, float pRadius, float pMinDistance, boolean shortest, boolean side) 
    { 

     List<PointF> pOutPut = new ArrayList<PointF>(); 

     // Calculate the middle of the two given points. 
     PointF mPoint = new PointF(pFrom.x + pTo.x, pFrom.y + pTo.y); 
     mPoint.x /= 2.0f; 
     mPoint.y /= 2.0f; 
     System.out.println("Middle Between From and To = " + mPoint); 


     // Calculate the distance between the two points 
     float xDiff = pTo.x - pFrom.x; 
     float yDiff = pTo.y - pFrom.y; 
     float distance = (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff); 
     System.out.println("Distance between From and To = " + distance); 

     if (pRadius * 2.0f < distance) { 
      throw new IllegalArgumentException("The radius is too small! The given points wont fall on the circle."); 
     } 

     // Calculate the middle of the expected curve. 
     float factor = (float) Math.sqrt((pRadius * pRadius)/((pTo.x - pFrom.x) * (pTo.x - pFrom.x) + (pTo.y - pFrom.y) * (pTo.y - pFrom.y)) - 0.25f); 
     PointF circleMiddlePoint = new PointF(0, 0); 
     if (side) { 
      circleMiddlePoint.x = 0.5f * (pFrom.x + pTo.x) + factor * (pTo.y - pFrom.y); 
      circleMiddlePoint.y = 0.5f * (pFrom.y + pTo.y) + factor * (pFrom.x - pTo.x); 
     } else { 
      circleMiddlePoint.x = 0.5f * (pFrom.x + pTo.x) - factor * (pTo.y - pFrom.y); 
      circleMiddlePoint.y = 0.5f * (pFrom.y + pTo.y) - factor * (pFrom.x - pTo.x); 
     } 
     System.out.println("Middle = " + circleMiddlePoint); 

     // Calculate the two reference angles 
     float angle1 = (float) Math.atan2(pFrom.y - circleMiddlePoint.y, pFrom.x - circleMiddlePoint.x); 
     float angle2 = (float) Math.atan2(pTo.y - circleMiddlePoint.y, pTo.x - circleMiddlePoint.x); 

     // Calculate the step. 
     float step = pMinDistance/pRadius; 
     System.out.println("Step = " + step); 

     // Swap them if needed 
     if (angle1 > angle2) { 
      float temp = angle1; 
      angle1 = angle2; 
      angle2 = temp; 

     } 
     boolean flipped = false; 
     if (!shortest) { 
      if (angle2 - angle1 < Math.PI) { 
       float temp = angle1; 
       angle1 = angle2; 
       angle2 = temp; 
       angle2 += Math.PI * 2.0f; 
       flipped = true; 
      } 
     } 
     for (float f = angle1; f < angle2; f += step) { 
      PointF p = new PointF((float) Math.cos(f) * pRadius + circleMiddlePoint.x, (float) Math.sin(f) * pRadius + circleMiddlePoint.y); 
      pOutPut.add(p); 
     } 
     if (flipped^side) { 
      pOutPut.add(pFrom); 
     } else { 
      pOutPut.add(pTo); 
     } 

     return pOutPut; 
    } 
} 

和使用generateCurve方法像这样具有和点之间的曲线..

generateCurve(pFrom, pTo, 100f, 7f, true, false); 
+0

差不多。当我尝试翻译它时,我收到了令人不快的结果。它在-a-圈上给我2点,但不是由点确定的圈。 (为了适应SSCCE我做了一些改动)我会稍微多玩一点。 –

1

好了,在这里是,测试和工作。这些问题是基于我不使用图形的事实,所以我必须提醒自己,坐标系是落后的,事实上构造函数的Javadoc描述是非常残酷的。

除了这些,我发现你的点创建(对于要连接的两个点)在给定要求时效率非常低。我假设你实际上必须得到两个任意点,然后然后计算它们的角度等,但根据你对Pastebin的设置,我们可以定义这两点,但我们可以。这对我们有利。

无论如何,这里是一个工作版本,没有从之前的gobbledegook。简化的代码被简化:

import javax.swing.JComponent; 
import java.awt.geom.*; 
import java.awt.*; 
import java.util.*; 

public class Canvas extends JComponent { 
    double circleX, circleY, x1, y1, x2, y2, dx, dy, dx2, dy2, radius, radius2; 
    Random random = new Random(); 
    double distance; 
    private static double theta1; 
    private static double theta2; 
    private static double theta; 
    // private static double radius; 
    private Point2D point1; 
    private Point2D point2; 
    private Point2D center; 
    private static int direction; 
    private static final int CW = -1; 
    private static final int CCW = 1; 

public Canvas() { 
    /* 
    * You want two random points on a circle, so let's start correctly, 
    * by setting a random *radius*, and then two random *angles*. 
    * 
    * This has the added benefit of giving us the angles without having to calculate them 
    */ 

    radius = random.nextInt(175); //your maximum radius is higher, but we only have 200 pixels in each cardinal direction 
    theta1 = random.nextInt(360); //angle to first point (absolute measurement) 
    theta2 = random.nextInt(360); //angle to second point 

    //build the points 
    center = new Point2D.Double(200, 200); //your frame is actually 400 pixels on a side 
    point1 = new Point2D.Double(radius * Math.cos(toRadians(theta1)) + center.getX(), center.getY() - radius * Math.sin(toRadians(theta1))); 
    point2 = new Point2D.Double(radius * Math.cos(toRadians(theta2)) + center.getX(), center.getY() - radius * Math.sin(toRadians(theta2))); 

    theta = Math.abs(theta1 - theta2) <= 180 ? Math.abs(theta1 - theta2) : 360 - (Math.abs(theta1 - theta2)); 

    if ((theta1 + theta) % 360 == theta2) { 
     direction = CCW; 
    } else { 
     direction = CW; 
    } 

    System.out.println("theta1: " + theta1 + "; theta2: " + theta2 + "; theta: " + theta + "; direction: " + (direction == CCW ? "CCW" : "CW")); 
    System.out.println("point1: (" + (point1.getX() - center.getX()) + ", " + (center.getY() - point1.getY()) + ")"); 
    System.out.println("point2: (" + (point2.getX() - center.getX()) + ", " + (center.getY() - point2.getY()) + ")"); 

    // Radius now equal for both points. 
} 

public double toRadians(double angle) { 
    return angle * Math.PI/180; 
} 

public double toDegrees(double angle) { 
    return angle * 180/Math.PI; 
} 

public void paintComponent(Graphics g2) { 
    Graphics2D g = (Graphics2D) g2; 
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
      RenderingHints.VALUE_ANTIALIAS_ON); 
    g.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_BUTT, 
      BasicStroke.JOIN_BEVEL)); 

    //centerpoint should be based on the actual center point 
    Arc2D.Double centerPoint = new Arc2D.Double(center.getX() - 2, center.getY() - 2, 4, 4, 0, 
      360, Arc2D.OPEN); 
    //likewise these points 
    Arc2D.Double point11 = new Arc2D.Double(point1.getX() - 2, point1.getY() - 2, 4, 4, 0, 360, 
      Arc2D.OPEN); 
    Arc2D.Double point22 = new Arc2D.Double(point2.getX() - 2, point2.getY() - 2, 4, 4, 0, 360, 
      Arc2D.OPEN); 

    // 3 points drawn in black 
    g.setColor(Color.BLACK); 
    g.draw(centerPoint); 
    g.draw(point11); 
    g.draw(point22); 

    // Arc drawn in blue 
    g.setColor(Color.BLUE); 
    g.draw(new Arc2D.Double(center.getX() - radius, center.getY() - radius, 2 * radius, 2 * radius, theta1, theta * direction, Arc2D.OPEN)); 
} 

}

+0

翻译完了,但它不能正常工作:http://pastebin.com/XXXGWa12 –

+0

它是否至少绘制了适当形状的弧线? – cabbagery

+0

不是,一切都是关闭的,有时候电弧从屏幕上脱落,并且颠倒。我不知道为什么中心关闭 - 我试图弄清楚这一点。该中心初始化为(150,150)。 –