Java在两点之间绘制弧形

时间:2013-05-21 12:46:47

标签: java trigonometry graphics2d

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

我设法让弧线在象限2中锁定到锚点(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

    }
}

3 个答案:

答案 0 :(得分: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方法在from和to之间有一条曲线..

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

答案 1 :(得分:1)

好的,这是测试和工作。问题是基于我不使用图形的事实,所以我必须提醒自己,坐标系是向后的,并且Arc2D构造函数的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));
}

}

答案 2 :(得分: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);
            }
        });
    }
}