用一条线连接两个圆圈

时间:2017-11-18 18:27:04

标签: java swing math graphics shapes

我在JPanel中绘制了两个形状(圆圈),我需要用线连接它们。我这样做只是通过获得圆圈的中间点并相互连接,很容易。

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

在我最后一次尝试结果时,没有什么好处:

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

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

编辑:圆圈是可移动的,它们可以处于任何位置,因此该线条无论如何都应该有效。

4 个答案:

答案 0 :(得分:4)

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

  1. 获取两个圆圈之间的角度
  2. 沿此角度从一个圆周围绘制一条直线
  3. 这两个问题都难以解决(任何时候花在搜索互联网上都会提供解决方案 - 因为我从那里获得了解决方案;)

    因此,两点之间的角度可以使用类似......

    的方法计算
    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);
    

    可运行示例

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

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

答案 1 :(得分:1)

让第一个圆心坐标为AX,AY,半径AR,以及第二个圆圈的BX,BY,BR。

差异向量

...
blurMaskFilterText = (TextView)findViewById(R.id.textView1);
BlurMaskFilter filter = new BlurMaskFilter((blurMaskFilterText.getTextSize()/10),BlurMaskFilter.Blur.INNER);
blurMaskFilterText.getPaint().setMaskFilter(filter);

归一化的

D = (DX, DY)  = (BX - AX, BY - AY)

箭头的起点

d = (dx, dy) = (DX / Length(D), DY / Length(D))

结束点

S = (sx, sy) = (AX + dx * AR, AY + dy * AR)  

示例:

E = (ex, ey) = (BX - dx * BR, BY - dy * BR)  

答案 2 :(得分:0)

查看Screenshot,我认为你需要找到圆圈A的右上角,然后将总距离的一半加到底部到y。接下来,找到圆圈B的右上角,并将左上角的一半距离添加到x。最后,做一条连接两者的线,并在它的末端渲染一个箭头 像这样:

private int x1, y1, x2, y2 width = 20, height = 20;

private void example(Graphics g) {
    // Set x1, x2, y1, and y2 to something
    g.drawOval(x1, y1, width, height);
    g.drawOval(x2, y2, width, height);
    g.drawLine(x1, y1 + (height/2), x2 + (width/2), y2);
    g.drawImage(/*Image of an arrow*/, (x2 + width/2)-2, y2);
}

答案 3 :(得分:0)

我的诀窍:

让两个中心为C0C1。使用复数,您可以通过转换将这两个点映射到原点的水平线段

P' = (P - C0) (C1 - C0)* / L

其中*表示共轭,L = |C1 - C0|。 (如果您不喜欢复数表示法,也可以用矩阵表示。)

现在,细分受众群的可见部分从(R0, 0)变为(L - R1, 0)。对于高度为(L - R1 - H, W)和宽度为(L - R1 - H, -W)的箭头,箭头的另外两个顶点位于H2W

通过应用逆变换,您将获得原始坐标

P = C0 + L P' / (C1 - C0)*.

enter image description here