簡體   English   中英

捕捉邊緣效應

[英]Snap to Edge Effect

在此輸入圖像描述

我的最終目標是有一個方法,讓我們說:

Rectangle snapRects(Rectangle rec1, Rectangle rec2);

想象一下一個Rectangle它有關於positionsizeangle

將ABDE矩形拖放到BCGF矩形附近將調用方法,ABDE作為第一個參數,BCGF作為第二個參數,生成的矩形是一個與BCGF邊緣對齊的矩形。

頂點不必匹配(並且最好不要匹配,因此捕捉不是那么嚴格)。

我只能輕易理解如何給出相同的角度,但是位置變化對我來說非常困惑。 此外,我相信即使我達成了一個解決方案,它將被非常優化(資源成本過高),所以我將很感激這方面的指導。

(已經有人問過,但沒有給出滿意的答案而忘記了這個問題。)

------------------------------------------------------------------

編輯:似乎我的解釋不夠,所以我會試着澄清我的意願:

下圖顯示了該方法的目標:

在此輸入圖像描述

忘了“最接近的矩形”,想象一下只有兩個矩形。 矩形內的線條代表它們所面對的方向(角度的視覺輔助)。

有一個靜態矩形,它不會被移動並且有一個角度(0-> 360),還有一個矩形(也有一個角度)我想要捕捉到靜態矩形的最近邊緣 通過這個,我的意思是,我希望“跳到邊緣”發生的可能性最小

這帶來了許多可能的情況,這取決於矩形的旋轉及其相對於彼此的位置。

下一個圖像顯示靜態矩形以及“To Snap”矩形的位置如何更改捕捉結果:

在此輸入圖像描述

最后的旋轉可能並不完美,因為它是由眼睛完成的,但是你得到了重點,重要的是相對位置和兩個角度。

現在,在我看來,這可能是完全天真的,我看到這個問題在轉換“To Snap”矩形的兩個重要且不同的步驟上得到了解決: 定位旋轉

位置 :新位置的目標是粘貼到最近的邊緣,但由於我們希望它將並行方法粘貼到靜態矩形,因此靜態矩形的角度很重要。 下圖顯示了定位示例:

在此輸入圖像描述

在這種情況下,靜態矩形沒有角度,因此很容易確定上,下,左和右。 但是角度來看,還有更多的可能性:

在此輸入圖像描述

至於旋轉,目標是“捕捉”矩形旋轉與靜態矩形成為並行所需的最小值

在此輸入圖像描述

作為最后一點,關於實現輸入,目標是實際將“to snap”矩形拖動到我希望圍繞靜態矩形的任何位置,並且通過按下鍵盤鍵,快照發生。

此外,當我要求優化時,似乎我已經誇大了一點,說實話我不需要或需要優化,我更喜歡一個易於閱讀,一步一步的明確代碼(如果是這樣),而不是任何優化一點都不

我希望這次我很清楚,對不起首先不清楚,如果你有任何疑問,請問。

問題顯然不明確:邊緣的“排列”是什么意思? 一個共同的起點(但不一定是一個共同的終點)? 兩個邊緣的共同中心點? (這就是我現在的假設)。 所有邊緣應該匹配嗎? 決定第一個矩形的邊緣應該與第二個矩形的邊緣“匹配”的標准是什么? 也就是說,想象一個正方形恰好包含另一個正方形邊緣的中心點 - 那么它應該如何對齊呢?

(次要問題:優化(或“低資源成本”)到底有多遠?)

但是,我寫了一些第一行,也許這可以用來更清楚地指出預期的行為應該是什么 - 即通過說出預期的行為與實際行為有多遠:

編輯:舊代碼省略,更新基於澄清:

“攫取”的條件仍然不明確。 例如,不清楚位置的變化或角度的變化是否應該是優選的。 但誠然,我沒有詳細說明可能出現這個問題的所有可能情況。 在任何情況下,根據更新的問題,這可能更接近您正在尋找的。

注意:此代碼既不“干凈”也不特別優雅或高效。 到目前為止的目標是找到一種能夠提供“令人滿意”結果的方法。 優化和美化是可能的。

基本理念:

  • 給定的是靜態矩形r1和要捕捉的矩形r0
  • 計算應該拼接在一起的邊。 這分為兩個步驟:
    • 方法computeCandidateEdgeIndices1計算可以捕捉到移動矩形的靜態矩形的“候選邊”(或它們的索引)。 這基於以下標准:它檢查移動矩形的多少個頂點(角)是否在特定邊緣的右側 例如,如果移動矩形的所有4個頂點都在邊緣2的右側 ,則邊緣2將是用於捕捉矩形的候選者。
    • 由於可能存在相同數量的頂點可能是“右”的多個邊,因此computeBestEdgeIndices方法計算候選邊,其中心與移動矩形的任何邊的中心具有最小距離。 返回相應邊的索引
  • 給定要捕捉的邊的索引,計算這些邊之間的角度。 生成的矩形將是原始矩形,按此角度旋轉。
  • 將移動旋轉的矩形,使捕捉的邊緣的中心位於同一點

我用幾種配置測試了這個,結果對我來說至少看起來“可行”。 當然,這並不意味着它在所有情況下都能令人滿意,但也許它可以作為一個起點。

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

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM