java - 用一条线连接两个圆

标签 java swing math graphics shapes

我在 JPanel 中绘制两个形状(圆形),我需要用一条线将它们连接起来。我通过简单地获取圆的中点并相互连接来做到这一点。

问题是现在我需要制作单向线,它的末端有一个“箭头”,以指出线的走向。所以现在我不能使用圆的中点,因为我需要从一个边界到另一个边界相互连接,这样“箭头”才能正确显示。

我最后一次尝试的结果是,没什么好:

PS:在屏幕截图中,我填充圆圈并不是为了查看线条的确切位置,但通常我会填充它。

我无法计算我需要开始/结束我的行的边界的确切位置。任何人都知道如何做到这一点?

编辑:圆圈是可移动的,它们可以在任何位置,所以这条线在任何情况下都应该有效。

最佳答案

好的,基本上,我们可以将问题分解为基本问题:

  1. 获取两个圆之间的夹角
  2. 沿着这个角度从一个圆的圆周画一条线到另一个圆

这两个问题都不难解决(花在互联网上的任何时间搜索都会提供解决方案 - 因为我就是从那里得到它们的;))

所以,两点之间的角度可以用类似...的方法来计算

protected double angleBetween(Point2D from, Point2D to) {
    double x = from.getX();
    double y = from.getY();

    // This is the difference between the anchor point
    // and the mouse.  Its important that this is done
    // within the local coordinate space of the component,
    // this means either the MouseMotionListener needs to
    // be registered to the component itself (preferably)
    // or the mouse coordinates need to be converted into
    // local coordinate space
    double deltaX = to.getX() - x;
    double deltaY = to.getY() - y;

    // Calculate the angle...
    // This is our "0" or start angle..
    double rotation = -Math.atan2(deltaX, deltaY);
    rotation = Math.toRadians(Math.toDegrees(rotation) + 180);

    return rotation;
}

圆上的点可以用类似...的方法计算

protected Point2D getPointOnCircle(Point2D center, double radians, double radius) {

    double x = center.getX();
    double y = center.getY();

    radians = radians - Math.toRadians(90.0); // 0 becomes the top
    // Calculate the outter point of the line
    double xPosy = Math.round((float) (x + Math.cos(radians) * radius));
    double yPosy = Math.round((float) (y + Math.sin(radians) * radius));

    return new Point2D.Double(xPosy, yPosy);

}

请注意,对结果进行了一些内部修改,以考虑数学解决方案与 Graphics API 绘制圆圈的方式之间的差异

好吧,你说的那么重要,这对我有什么帮助?好吧,实际上我很厉害。

您需要计算圆圈之间的角度(往返圆圈之间的角度,您也许可以简单地反转一个角度,但我有可用的计算方法,所以我使用了它)。由此,您可以计算出每个圆上的直线相交点,然后您只需绘制它,例如...

double from = angleBetween(circle1, circle2);
double to = angleBetween(circle2, circle1);

Point2D pointFrom = getPointOnCircle(circle1, from);
Point2D pointTo = getPointOnCircle(circle2, to);

Line2D line = new Line2D.Double(pointFrom, pointTo);
g2d.draw(line);

可运行示例

因为我已经将大部分计算提炼为公共(public)属性,所以我提供了我的测试代码作为可运行的示例。所有的计算都是基于动态值,没有什么是真正硬编码的。例如,您可以更改圆圈的大小和位置,计算应该会继续进行...

Example

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private Ellipse2D circle1;
        private Ellipse2D circle2;

        private Point2D drawTo;

        public TestPane() {
            circle1 = new Ellipse2D.Double(10, 10, 40, 40);
            circle2 = new Ellipse2D.Double(100, 150, 40, 40);

            //addMouseMotionListener(new MouseAdapter() {
            //  @Override
            //  public void mouseMoved(MouseEvent e) {
            //      drawTo = new Point2D.Double(e.getPoint().x, e.getPoint().y);
            //      repaint();
            //  }
            //});
        }

        protected Point2D center(Rectangle2D bounds) {
            return new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
        }

        protected double angleBetween(Shape from, Shape to) {
            return angleBetween(center(from.getBounds2D()), center(to.getBounds2D()));
        }

        protected double angleBetween(Point2D from, Point2D to) {
            double x = from.getX();
            double y = from.getY();

            // This is the difference between the anchor point
            // and the mouse.  Its important that this is done
            // within the local coordinate space of the component,
            // this means either the MouseMotionListener needs to
            // be registered to the component itself (preferably)
            // or the mouse coordinates need to be converted into
            // local coordinate space
            double deltaX = to.getX() - x;
            double deltaY = to.getY() - y;

            // Calculate the angle...
            // This is our "0" or start angle..
            double rotation = -Math.atan2(deltaX, deltaY);
            rotation = Math.toRadians(Math.toDegrees(rotation) + 180);

            return rotation;
        }

        protected Point2D getPointOnCircle(Shape shape, double radians) {
            Rectangle2D bounds = shape.getBounds();
//          Point2D point = new Point2D.Double(bounds.getX(), bounds.getY());
            Point2D point = center(bounds);
            return getPointOnCircle(point, radians, Math.max(bounds.getWidth(), bounds.getHeight()) / 2d);
        }

        protected Point2D getPointOnCircle(Point2D center, double radians, double radius) {

            double x = center.getX();
            double y = center.getY();

            radians = radians - Math.toRadians(90.0); // 0 becomes th?e top
            // Calculate the outter point of the line
            double xPosy = Math.round((float) (x + Math.cos(radians) * radius));
            double yPosy = Math.round((float) (y + Math.sin(radians) * radius));

            return new Point2D.Double(xPosy, yPosy);

        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.draw(circle1);
            g2d.draw(circle2);

            // This was used for testing, it will draw a line from circle1 to the
            // drawTo point, which, if enabled, is the last known position of the
            // mouse
            //if (drawTo != null) {
            //  Point2D pointFrom = center(circle1.getBounds2D());
            //  g2d.setColor(Color.RED);
            //  g2d.draw(new Line2D.Double(drawTo, pointFrom));
            //
            //  double from = angleBetween(pointFrom, drawTo);
            //  System.out.println(NumberFormat.getNumberInstance().format(Math.toDegrees(from)));
            //
            //  Point2D poc = getPointOnCircle(circle1, from);
            //  g2d.setColor(Color.BLUE);
            //  g2d.draw(new Line2D.Double(poc, drawTo));
            //}

            double from = angleBetween(circle1, circle2);
            double to = angleBetween(circle2, circle1);

            Point2D pointFrom = getPointOnCircle(circle1, from);
            Point2D pointTo = getPointOnCircle(circle2, to);

            g2d.setColor(Color.RED);
            Line2D line = new Line2D.Double(pointFrom, pointTo);
            g2d.draw(line);
            g2d.dispose();
        }

    }

}

箭头

目的是将箭头视为一个单独的实体。原因是因为这样更简单,无论对象之间的距离如何,您都可以获得更一致的结果。

所以,首先,我定义了一个新的 Shape...

public class ArrowHead extends Path2D.Double {

    public ArrowHead() {
        int size = 10;
        moveTo(0, size);
        lineTo(size / 2, 0);
        lineTo(size, size);
    }
    
}

真的很简单。它只是创建两条线,指向上方,在可用空间的中间相遇。

然后在 paintComponent 方法中,我们使用我们已有的可用信息执行一些 AffineTransform 魔法,即

  • 目标圆周上的点
  • 与目标圆的角度

并转换 ArrowHead 形状...

g2d.setColor(Color.MAGENTA);
ArrowHead arrowHead = new ArrowHead();
AffineTransform at = AffineTransform.getTranslateInstance(
                pointTo.getX() - (arrowHead.getBounds2D().getWidth() / 2d), 
                pointTo.getY());
at.rotate(from, arrowHead.getBounds2D().getCenterX(), 0);
arrowHead.transform(at);
g2d.draw(arrowHead);

Pointy

现在,因为我疯了,我还通过绘制一个指向我们的源圆圈的箭头来测试代码,只是为了证明计算是可行的......

// This just proofs that the previous calculations weren't a fluke
// and that the arrow can be painted pointing to the source object as well
g2d.setColor(Color.GREEN);
arrowHead = new ArrowHead();
at = AffineTransform.getTranslateInstance(
                pointFrom.getX() - (arrowHead.getBounds2D().getWidth() / 2d), 
                pointFrom.getY());
at.rotate(to, arrowHead.getBounds2D().getCenterX(), 0);
arrowHead.transform(at);
g2d.draw(arrowHead);

关于java - 用一条线连接两个圆,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47369565/

相关文章:

java - 有没有好的反模式来源?

java - 如何同步共享变量使这个程序安全?

java - Servlet 路径信息包含空字符串

java - JlayeredPane 透明度 setOpaque 不起作用

java - 如何在 n 个敌人上生成 k 次射击的分布

java - java中synchronized(expr){}中expr的意义

Java同步问题

java - java无法解析textArea

c# - 将除以 10 的整数四舍五入到最接近的整数

c - 矩阵求逆