Java在两点之间绘制圆弧

时间:2023-02-09 15:47:48

I'm having trouble drawing the smallest arc described by 3 points: the arc center, an "anchored" end point, and a second point that gives the other end of the arc by determining a radius. I used the law of cosines to determine the length of the arc and tried using atan for the starting degree, but the starting position for the arc is off.

我画出由3个点描述的最小的圆弧有困难:圆弧中心,一个“锚定的”端点,第二个点通过确定半径来给出圆弧的另一端。我用余弦定理来确定弧的长度,并尝试用atan来表示起始度,但是弧的起始位置是off的。

I managed to get the arc to lock onto the anchor point (x1,y1) when it's in Quadrant 2, but that will only work when it is in Quadrant 2.

当它在象限2时,我设法使弧线锁定在锚点(x1,y1)上,但只有当它在象限2时,它才能工作。

Solutions I can see all have a bunch of if-statements to determine the location of the 2 points relative to each other, but I'm curious if I'm overlooking something simple. Any help would be greatly appreciated.

我可以看到所有的解决方案都有一堆if语句来确定两个点相对的位置,但是我很好奇我是否忽略了一些简单的东西。如有任何帮助,我们将不胜感激。

SSCCE:

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

    }
}

3 个解决方案

#1


1  

Perhaps this will help. It tests with click and drag to set the two points rather than random numbers. It's considerably simpler than what you were attempting and other solutions posted so far.

也许这将帮助。它通过点击和拖动来设置两个点,而不是随机数。这比您目前尝试的和发布的其他解决方案要简单得多。

Notes:

注:

  • Math.atan2() is a friend in problems like this.
  • Math.atan2()是处理此类问题的朋友。
  • Little helper functions make it easier to reason about your code.
  • 小助手函数使您更容易理解代码。
  • It's best practice to use instance variables for independent values only and compute the dependent values in local variables.
  • 最好只对独立值使用实例变量,并计算局部变量中的因变量。
  • My code fixes some Swing usage problems like calling Swing functions from the main thread.
  • 我的代码修复了一些Swing使用问题,比如从主线程调用Swing函数。

Code follows:

代码如下:

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);
            }
        });
    }
}

#2


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;
    }
}

and the use the generateCurve method like this to have a curve between the from and to points..

使用类似的generateCurve方法在从点到点之间有一条曲线。

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

#3


1  

Okay, here it is, testing and working. The problems were based on the fact that I don't use graphics much, so I have to remind myself that the coordinate systems are backward, and on the fact that the Javadoc description of the Arc2D constructor is atrocious.

好的,在这里,测试和工作。问题是基于这样一个事实:我不怎么使用图形,所以我必须提醒自己,坐标系统是向后的,而且Arc2D构造函数的Javadoc描述非常糟糕。

In addition to these, I found that your point creation (for the two points to be connected) was extremely inefficient given the requirements. I had assumed you actually had to receive two arbitrary points and then calculate their angles, etc., but based on what you put on Pastebin, we can define the two points however we please. This benefits us.

除此之外,我发现您的点创建(对于要连接的两个点)在给定需求时效率极低。我假设你需要接收两个任意的点然后计算它们的角度,等等,但是根据你在Pastebin上的内容,我们可以随意定义这两个点。这给我们带来了好处。

Anyway, here's a working version, with none of that gobbledegook from before. Simplified code is simplified:

不管怎样,这是一个有效的版本,没有以前的官样文章。简化代码简化:

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));
}

}

}

#1


1  

Perhaps this will help. It tests with click and drag to set the two points rather than random numbers. It's considerably simpler than what you were attempting and other solutions posted so far.

也许这将帮助。它通过点击和拖动来设置两个点,而不是随机数。这比您目前尝试的和发布的其他解决方案要简单得多。

Notes:

注:

  • Math.atan2() is a friend in problems like this.
  • Math.atan2()是处理此类问题的朋友。
  • Little helper functions make it easier to reason about your code.
  • 小助手函数使您更容易理解代码。
  • It's best practice to use instance variables for independent values only and compute the dependent values in local variables.
  • 最好只对独立值使用实例变量,并计算局部变量中的因变量。
  • My code fixes some Swing usage problems like calling Swing functions from the main thread.
  • 我的代码修复了一些Swing使用问题,比如从主线程调用Swing函数。

Code follows:

代码如下:

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);
            }
        });
    }
}

#2


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;
    }
}

and the use the generateCurve method like this to have a curve between the from and to points..

使用类似的generateCurve方法在从点到点之间有一条曲线。

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

#3


1  

Okay, here it is, testing and working. The problems were based on the fact that I don't use graphics much, so I have to remind myself that the coordinate systems are backward, and on the fact that the Javadoc description of the Arc2D constructor is atrocious.

好的,在这里,测试和工作。问题是基于这样一个事实:我不怎么使用图形,所以我必须提醒自己,坐标系统是向后的,而且Arc2D构造函数的Javadoc描述非常糟糕。

In addition to these, I found that your point creation (for the two points to be connected) was extremely inefficient given the requirements. I had assumed you actually had to receive two arbitrary points and then calculate their angles, etc., but based on what you put on Pastebin, we can define the two points however we please. This benefits us.

除此之外,我发现您的点创建(对于要连接的两个点)在给定需求时效率极低。我假设你需要接收两个任意的点然后计算它们的角度,等等,但是根据你在Pastebin上的内容,我们可以随意定义这两个点。这给我们带来了好处。

Anyway, here's a working version, with none of that gobbledegook from before. Simplified code is simplified:

不管怎样,这是一个有效的版本,没有以前的官样文章。简化代码简化:

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));
}

}

}