Java 捕捉到边效果

Java 捕捉到边效果,java,algorithm,math,methods,rectangles,Java,Algorithm,Math,Methods,Rectangles,我的最终目标是找到一种方法,比如说: Rectangle snapRects(Rectangle rec1, Rectangle rec2); 想象一个矩形有关于位置、大小和角度的信息 将ABDE矩形拖放到BCGF矩形附近将调用ABDE作为第一个参数、BCGF作为第二个参数的方法,得到的矩形是一个与BCGF的边对齐的矩形 顶点不必匹配(最好是不匹配,这样捕捉就不会那么严格) 我只能很容易地理解如何给出相同的角度,但位置的变化对我来说是相当混乱的。此外,我相信即使我找到了一个解决方案,它也会非常

我的最终目标是找到一种方法,比如说:

Rectangle snapRects(Rectangle rec1, Rectangle rec2);
想象一个
矩形
有关于
位置
大小
角度
的信息

将ABDE矩形拖放到BCGF矩形附近将调用ABDE作为第一个参数、BCGF作为第二个参数的方法,得到的矩形是一个与BCGF的边对齐的矩形

顶点不必匹配(最好是不匹配,这样捕捉就不会那么严格)


我只能很容易地理解如何给出相同的角度,但位置的变化对我来说是相当混乱的。此外,我相信即使我找到了一个解决方案,它也会非常糟糕地优化(过度的资源成本),因此我希望得到这方面的指导

(已经提出了这个问题,但没有给出令人满意的答案,问题也被遗忘了。)

编辑:看来我的解释不够充分,所以我将尝试澄清我的愿望:

下图概括地显示了该方法的目标:

忘掉“最近的矩形”,想象一下只有两个矩形。矩形内的线表示它们所面对的方向(角度的视觉辅助)

有一个静态矩形,不可移动,有一个角度(0->360),还有一个矩形(也有一个角度),我想捕捉到静态矩形的最近边。我的意思是,我希望“捕捉到边缘”的变换尽可能少

这带来了许多可能的情况,这取决于矩形的旋转及其相对位置

下图显示了静态矩形以及“要捕捉”矩形的位置如何更改捕捉结果:

最终的旋转可能不是完美的,因为它是由眼睛完成的,但你得到了点,它关系到相对位置和两个角度

现在,在我看来,这可能是完全幼稚的,我看到这个问题在转换“捕捉”矩形的两个重要而不同的步骤上得到了解决:定位旋转

位置:新位置的目标是粘贴到最近的边,但由于我们希望它与静态矩形平行,因此静态矩形的角度很重要。下图显示了定位示例:

在这种情况下,静态矩形没有角度,因此很容易确定上、下、左和右。但有了角度,还有更多的可能性:

至于旋转,目标是使“捕捉”矩形旋转与静态矩形平行所需的最小值

最后一点,关于实现输入,目标是实际地将“to snap”矩形拖动到我希望在静态矩形周围的任何位置,并通过按键盘键进行捕捉

此外,当我要求优化时,我似乎有点夸张,老实说,我不需要或不需要优化,我更喜欢一个易于阅读、一步一步清晰的代码(如果是这样的话),而不是任何优化


我希望这一次我说得很清楚,首先很抱歉不够清楚,如果您还有任何疑问,请一定要问。

问题显然没有明确说明:边缘的“对齐”是什么意思?共同的起点(但不一定是共同的终点)?两条边的公共中心点?(这就是我现在的想法)。所有的边都应该匹配吗?决定第一个矩形的哪条边应与第二个矩形的哪条边“匹配”的标准是什么?也就是说,想象一个正方形正好由另一个正方形边缘的中心点组成——那个么它应该如何对齐呢

(第二个问题:优化(或“低资源成本”)有多重要?)

然而,我写了几行,也许这可以用来更清楚地指出预期行为应该是什么——也就是说,预期行为与实际行为的差异有多大:

编辑:省略旧代码,根据澄清进行更新:

“抓拍”的条件仍然不明确。例如,不清楚是否应首选位置变化或角度变化。但无可否认,我并没有详细指出所有可能出现这个问题的案例。在任何情况下,根据更新后的问题,这可能更接近您所寻找的内容

注意:此代码既不“干净”,也不特别优雅或高效。到目前为止,我们的目标是找到一种能产生“令人满意”结果的方法。优化和美化是可能的

基本思想是:

  • 给定的是静态矩形r1和要捕捉的矩形r0
  • 计算应捕捉在一起的边。这分为两个步骤:
    • 方法
      ComputeCandidatedGeIndicates1
      计算移动矩形可能捕捉到的静态矩形的“候选边”(及其索引)。这是基于以下标准:它检查移动矩形中有多少顶点(角点)位于特定边的右侧。例如,如果移动矩形的所有4个顶点都位于边2的右侧,则边2将是将矩形捕捉到的候选对象
    • 由于可能有多条边,其相同数量的顶点可能是“正确的”,因此方法
      computebestedgeindex
      计算候选边,其中心与移动矩形的任何边的中心的距离最小。返回相应边的索引
  • 给定要捕捉的边的索引,将计算这些边之间的角度。生成的矩形将是原始矩形,按此角度旋转
    ------------------------------------------------------------------
    
    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.event.MouseEvent;
    import java.awt.event.MouseMotionListener;
    import java.awt.geom.AffineTransform;
    import java.awt.geom.Ellipse2D;
    import java.awt.geom.Line2D;
    import java.awt.geom.Point2D;
    import java.awt.geom.Rectangle2D;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    
    
    public class RectangleSnap
    {
        public static void main(String[] args)
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                @Override
                public void run()
                {
                    createAndShowGUI();
                }
            });
        }
    
        private static void createAndShowGUI()
        {
            JFrame f = new JFrame();
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            RectangleSnapPanel panel = new RectangleSnapPanel();
            f.getContentPane().add(panel);
    
            f.setSize(1000,1000);
            f.setLocationRelativeTo(null);
            f.setVisible(true);
        }
    }
    
    class SnapRectangle
    {
        private Point2D position;
        private double sizeX;
        private double sizeY;
        private double angleRad;
    
        private AffineTransform at;
    
    
        SnapRectangle(
            double x, double y, 
            double sizeX, double sizeY, double angleRad)
        {
            this.position = new Point2D.Double(x,y);
            this.sizeX = sizeX;
            this.sizeY = sizeY;
            this.angleRad = angleRad;
    
            at = AffineTransform.getRotateInstance(
                angleRad, position.getX(), position.getY());
        }
    
        double getAngleRad()
        {
            return angleRad;
        }
    
        double getSizeX()
        {
            return sizeX;
        }
    
        double getSizeY()
        {
            return sizeY;
        }
    
        Point2D getPosition()
        {
            return position;
        }
    
        void draw(Graphics2D g)
        {
            Color oldColor = g.getColor();
    
            Rectangle2D r = new Rectangle2D.Double(
                position.getX(),  position.getY(), sizeX,  sizeY);
            AffineTransform at = AffineTransform.getRotateInstance(
                angleRad, position.getX(), position.getY());
            g.draw(at.createTransformedShape(r));
    
            g.setColor(Color.RED);
            for (int i=0; i<4; i++)
            {
                Point2D c = getCorner(i);
                Ellipse2D e = new Ellipse2D.Double(c.getX()-3, c.getY()-3, 6, 6);
                g.fill(e);
                g.drawString(""+i, (int)c.getX(), (int)c.getY()+15);
            }
    
            g.setColor(Color.GREEN);
            for (int i=0; i<4; i++)
            {
                Point2D c = getEdgeCenter(i);
                Ellipse2D e = new Ellipse2D.Double(c.getX()-3, c.getY()-3, 6, 6);
                g.fill(e);
                g.drawString(""+i, (int)c.getX(), (int)c.getY()+15);
            }
    
            g.setColor(oldColor);
        }
    
        Point2D getCorner(int i)
        {
            switch (i)
            {
                case 0:
                    return new Point2D.Double(position.getX(), position.getY());
                case 1:
                {
                    Point2D.Double result = new Point2D.Double(
                        position.getX(), position.getY()+sizeY);
                    return at.transform(result, null);
                }
                case 2:
                {
                    Point2D.Double result = new Point2D.Double
                        (position.getX()+sizeX, position.getY()+sizeY);
                    return at.transform(result, null);
                }
                case 3:
                {
                    Point2D.Double result = new Point2D.Double(
                        position.getX()+sizeX, position.getY());
                    return at.transform(result, null);
                }
            }
            return null;
        }
    
        Line2D getEdge(int i)
        {
            Point2D p0 = getCorner(i); 
            Point2D p1 = getCorner((i+1)%4);
            return new Line2D.Double(p0, p1);
        }
    
        Point2D getEdgeCenter(int i)
        {
            Point2D p0 = getCorner(i); 
            Point2D p1 = getCorner((i+1)%4);
            Point2D c = new Point2D.Double(
                p0.getX() + 0.5 * (p1.getX() - p0.getX()),
                p0.getY() + 0.5 * (p1.getY() - p0.getY()));
            return c;
        }
    
        void setPosition(double x, double y)
        {
            this.position.setLocation(x, y);
            at = AffineTransform.getRotateInstance(
                angleRad, position.getX(), position.getY());
        }
    }
    
    
    class RectangleSnapPanel extends JPanel implements MouseMotionListener
    {
        private final SnapRectangle rectangle0;
        private final SnapRectangle rectangle1;
        private SnapRectangle snappedRectangle0;
    
        RectangleSnapPanel()
        {
            this.rectangle0 = new SnapRectangle(
                200, 300, 250, 200, Math.toRadians(-21));
            this.rectangle1 = new SnapRectangle(
                500, 300, 200, 150, Math.toRadians(36));
            addMouseMotionListener(this);
        }
    
        @Override
        protected void paintComponent(Graphics gr)
        {
            super.paintComponent(gr);
            Graphics2D g = (Graphics2D)gr;
    
            g.setColor(Color.BLACK);
            rectangle0.draw(g);
            rectangle1.draw(g);
            if (snappedRectangle0 != null)
            {
                g.setColor(Color.BLUE);
                snappedRectangle0.draw(g);
            }
        }
    
        @Override
        public void mouseDragged(MouseEvent e)
        {
            rectangle0.setPosition(e.getX(), e.getY());
    
            snappedRectangle0 = snapRects(rectangle0, rectangle1);
    
            repaint();
        }
    
        @Override
        public void mouseMoved(MouseEvent e)
        {
        }
    
    
        private static SnapRectangle snapRects(
            SnapRectangle r0, SnapRectangle r1)
        {
            List<Integer> candidateEdgeIndices1 = 
                computeCandidateEdgeIndices1(r0, r1);
    
            int bestEdgeIndices[] = computeBestEdgeIndices(
                r0, r1, candidateEdgeIndices1);
    
            int bestEdgeIndex0 = bestEdgeIndices[0];
            int bestEdgeIndex1 = bestEdgeIndices[1];
    
            System.out.println("Best to snap "+bestEdgeIndex0+" to "+bestEdgeIndex1);
    
            Line2D bestEdge0 = r0.getEdge(bestEdgeIndex0);
            Line2D bestEdge1 = r1.getEdge(bestEdgeIndex1);
            double edgeAngle = angleRad(bestEdge0, bestEdge1);
            double rotationAngle = edgeAngle;
    
            if (rotationAngle <= Math.PI)
            {
                rotationAngle = Math.PI + rotationAngle;
            }
            else if (rotationAngle <= -Math.PI / 2)
            {
                rotationAngle = Math.PI + rotationAngle;
            }
            else if (rotationAngle >= Math.PI)
            {
                rotationAngle = -Math.PI + rotationAngle;
            }
    
            SnapRectangle result = new SnapRectangle(
                r0.getPosition().getX(), r0.getPosition().getY(), 
                r0.getSizeX(), r0.getSizeY(), r0.getAngleRad()-rotationAngle);
    
            Point2D edgeCenter0 = result.getEdgeCenter(bestEdgeIndex0);
            Point2D edgeCenter1 = r1.getEdgeCenter(bestEdgeIndex1);
            double dx = edgeCenter1.getX() - edgeCenter0.getX();
            double dy = edgeCenter1.getY() - edgeCenter0.getY();
            result.setPosition(
                r0.getPosition().getX()+dx,
                r0.getPosition().getY()+dy);
    
            return result;
        }
    
        // Compute for the edge indices for r1 in the given list
        // the one that has the smallest distance to any edge
        // of r0, and return this pair of indices
        private static int[] computeBestEdgeIndices(
            SnapRectangle r0, SnapRectangle r1,
            List<Integer> candidateEdgeIndices1)
        {
            int bestEdgeIndex0 = -1;
            int bestEdgeIndex1 = -1;
            double minCenterDistance = Double.MAX_VALUE;
            for (int i=0; i<candidateEdgeIndices1.size(); i++)
            {
                int edgeIndex1 = candidateEdgeIndices1.get(i);
                for (int edgeIndex0=0; edgeIndex0<4; edgeIndex0++)
                {
                    Point2D p0 = r0.getEdgeCenter(edgeIndex0);
                    Point2D p1 = r1.getEdgeCenter(edgeIndex1);
                    double distance = p0.distance(p1);
                    if (distance < minCenterDistance)
                    {
                        minCenterDistance = distance;
                        bestEdgeIndex0 = edgeIndex0;
                        bestEdgeIndex1 = edgeIndex1;
                    }
                }
            }
            return new int[]{ bestEdgeIndex0, bestEdgeIndex1 };
        }
    
        // Compute the angle, in radians, between the given lines,
        // in the range (-2*PI, 2*PI)
        private static double angleRad(Line2D line0, Line2D line1)
        {
            double dx0 = line0.getX2() - line0.getX1();
            double dy0 = line0.getY2() - line0.getY1();
            double dx1 = line1.getX2() - line1.getX1();
            double dy1 = line1.getY2() - line1.getY1();
            double a0 = Math.atan2(dy0, dx0);
            double a1 = Math.atan2(dy1, dx1);
            return (a0 - a1) % (2 * Math.PI);
        }
    
        // In these methods, "right" refers to screen coordinates, which 
        // unfortunately are upside down in Swing. Mathematically, 
        // these relation is "left"
    
        // Compute the "candidate" edges of r1 to which r0 may
        // be snapped. These are the edges to which the maximum
        // number of corners of r0 are right of 
        private static List<Integer> computeCandidateEdgeIndices1(
            SnapRectangle r0, SnapRectangle r1)
        {
            List<Integer> bestEdgeIndices = new ArrayList<Integer>();
            int maxRight = 0;
            for (int i=0; i<4; i++)
            {
                Line2D e1 = r1.getEdge(i);
                int right = countRightOf(e1, r0);
                if (right > maxRight)
                {
                    maxRight = right;
                    bestEdgeIndices.clear();
                    bestEdgeIndices.add(i);
                }
                else if (right == maxRight)
                {
                    bestEdgeIndices.add(i);
                }
            }
            //System.out.println("Candidate edges "+bestEdgeIndices);
            return bestEdgeIndices;
        }
    
        // Count the number of corners of the given rectangle
        // that are right of the given line
        private static int countRightOf(Line2D line, SnapRectangle r)
        {
            int count = 0;
            for (int i=0; i<4; i++)
            {
                if (isRightOf(line, r.getCorner(i)))
                {
                    count++;
                }
            }
            return count;
        }
    
        // Returns whether the given point is right of the given line
        // (referring to the actual line *direction* - not in terms 
        // of coordinates in 2D!)
        private static boolean isRightOf(Line2D line, Point2D point)
        {
            double d00 = line.getX1() - point.getX();
            double d01 = line.getY1() - point.getY();
            double d10 = line.getX2() - point.getX();
            double d11 = line.getY2() - point.getY();
            return d00 * d11 - d10 * d01 > 0;
        }
    
    }