【问题标题】:An algorithm to space out overlapping rectangles?一种将重叠矩形隔开的算法?
【发布时间】:2019-05-29 18:20:19
【问题描述】:

这个问题实际上涉及翻转,我将在下面这样概括:

我有一个 2D 视图,并且我在屏幕上的一个区域内有许多矩形。如何展开这些框,使它们不相互重叠,而仅以最小的移动进行调整?

矩形的位置是动态的并且取决于用户的输入,因此它们的位置可以在任何地方。

附加的 图片显示了问题和所需的解决方案

实际上,现实生活中的问题涉及翻转。

cmets中问题的答案

  1. 矩形的大小不固定,取决于翻转中文本的长度

  2. 关于屏幕尺寸,现在我认为最好假设屏幕尺寸对于矩形来说已经足够了。如果矩形太多并且算法没有产生解决方案,那么我只需要调整内容。

  3. “最小限度地移动”的要求更多地是为了美学而不是绝对的工程要求。可以通过在它们之间添加很大的距离来隔开两个矩形,但作为 GUI 的一部分,它看起来并不好。这个想法是让翻转/矩形尽可能接近其源(然后我将用黑线连接到源)。因此,“只为 x 移动一个”或“为一半 x 移动两个”都可以。

【问题讨论】:

  • 我们可以假设矩形总是水平或垂直定向,而不是在它们的轴上倾斜一个角度吗?
  • 是的,假设是有效的。
  • 我们可以假设屏幕总是足够大以支持矩形而不重叠吗?矩形总是相同的大小吗?您能否更具体地了解“最小移动”的含义?例如,如果您有 2 个矩形正好位于彼此的顶部,最好只将其中 1 个矩形移到整个距离以消除重叠,还是将两个矩形移动一半距离?
  • @NickLarsen,我已经在上面编辑的答案中回答了你的问题。谢谢!
  • @joe:也许他想了解解决方案,所以他可以支持它。

标签: algorithm user-interface language-agnostic graphics


【解决方案1】:

我在这方面做了一些工作,因为我也需要类似的东西,但我推迟了算法的开发。你帮我得到了一些冲动:D

我还需要源代码,所以在这里。我在 Mathematica 中解决了它,但由于我没有大量使用函数特性,我想它很容易翻译成任何程序语言。

历史观点

首先我决定开发圆的算法,因为交点更容易计算。它仅取决于中心和半径。

我能够使用 Mathematica 方程求解器,它的性能很好。

看看:

这很容易。我刚刚加载了具有以下问题的求解器:

For each circle
 Solve[
  Find new coördinates for the circle
  Minimizing the distance to the geometric center of the image
  Taking in account that
      Distance between centers > R1+R2 *for all other circles
      Move the circle in a line between its center and the 
                                         geometric center of the drawing
   ]

就这么简单,Mathematica 完成了所有工作。

我说“哈!这很简单,现在让我们来看看矩形吧!”。但我错了……

矩形蓝色

矩形的主要问题是查询交叉点是一个讨厌的函数。比如:

因此,当我试图用很多这些条件来为 Mathematica 提供方程时,它的表现非常糟糕,以至于我决定做一些程序性的事情。

我的算法最终如下:

Expand each rectangle size by a few points to get gaps in final configuration
While There are intersections
    sort list of rectangles by number of intersections
    push most intersected rectangle on stack, and remove it from list
// Now all remaining rectangles doesn't intersect each other
While stack not empty
    pop  rectangle from stack and re-insert it into list
    find the geometric center G of the chart (each time!)
    find the movement vector M (from G to rectangle center)
    move the rectangle incrementally in the direction of M (both sides) 
                                                 until no intersections  
Shrink the rectangles to its original size

您可能会注意到“最小移动”条件并未完全满足(仅在一个方向上)。但是我发现向任何方向移动矩形来满足它,有时最终会导致用户感到困惑的地图发生变化。

在设计用户界面时,我选择将矩形移动得更远一点,但以更可预测的方式。您可以更改算法以检查其当前位置周围的所有角度和所有半径,直到找到一个空白位置,尽管它会要求更高。

无论如何,这些是结果示例(之前/之后):

编辑>更多示例here

如您所见,“最小运动”并不满足,但结果已经足够好。

我将在此处发布代码,因为我的 SVN 存储库遇到了一些问题。问题解决后我会删除它。

编辑:

您也可以使用R-Trees 来查找矩形交点,但对于处理少量矩形来说似乎有点过头了。而且我还没有实现算法。也许其他人可以将您指向您选择的平台上的现有实现。

警告!代码是第一种方法.. 质量还不是很好,而且肯定有一些错误。

这是数学。

(*Define some functions first*)

Clear["Global`*"];
rn[x_] := RandomReal[{0, x}];
rnR[x_] := RandomReal[{1, x}];
rndCol[] := RGBColor[rn[1], rn[1], rn[1]];

minX[l_, i_] := l[[i]][[1]][[1]]; (*just for easy reading*)
maxX[l_, i_] := l[[i]][[1]][[2]];
minY[l_, i_] := l[[i]][[2]][[1]];
maxY[l_, i_] := l[[i]][[2]][[2]];
color[l_, i_]:= l[[i]][[3]];

intersectsQ[l_, i_, j_] := (* l list, (i,j) indexes, 
                              list={{x1,x2},{y1,y2}} *) 
                           (*A rect does intesect with itself*)
          If[Max[minX[l, i], minX[l, j]] < Min[maxX[l, i], maxX[l, j]] &&
             Max[minY[l, i], minY[l, j]] < Min[maxY[l, i], maxY[l, j]], 
                                                           True,False];

(* Number of Intersects for a Rectangle *)
(* With i as index*)
countIntersects[l_, i_] := 
          Count[Table[intersectsQ[l, i, j], {j, 1, Length[l]}], True]-1;

(*And With r as rectangle *)
countIntersectsR[l_, r_] := (
    Return[Count[Table[intersectsQ[Append[l, r], Length[l] + 1, j], 
                       {j, 1, Length[l] + 1}], True] - 2];)

(* Get the maximum intersections for all rectangles*)
findMaxIntesections[l_] := Max[Table[countIntersects[l, i], 
                                       {i, 1, Length[l]}]];

(* Get the rectangle center *)
rectCenter[l_, i_] := {1/2 (maxX[l, i] + minX[l, i] ), 
                       1/2 (maxY[l, i] + minY[l, i] )};

(* Get the Geom center of the whole figure (list), to move aesthetically*)
geometryCenter[l_] :=  (* returs {x,y} *)
                      Mean[Table[rectCenter[l, i], {i, Length[l]}]]; 

(* Increment or decr. size of all rects by a bit (put/remove borders)*)
changeSize[l_, incr_] :=
                 Table[{{minX[l, i] - incr, maxX[l, i] + incr},
                        {minY[l, i] - incr, maxY[l, i] + incr},
                        color[l, i]},
                        {i, Length[l]}];

sortListByIntersections[l_] := (* Order list by most intersecting Rects*)
        Module[{a, b}, 
               a = MapIndexed[{countIntersectsR[l, #1], #2} &, l];
               b = SortBy[a, -#[[1]] &];
               Return[Table[l[[b[[i]][[2]][[1]]]], {i, Length[b]}]];
        ];

(* Utility Functions*)
deb[x_] := (Print["--------"]; Print[x]; Print["---------"];)(* for debug *)
tableForPlot[l_] := (*for plotting*)
                Table[{color[l, i], Rectangle[{minX[l, i], minY[l, i]},
                {maxX[l, i], maxY[l, i]}]}, {i, Length[l]}];

genList[nonOverlap_, Overlap_] :=    (* Generate initial lists of rects*)
      Module[{alist, blist, a, b}, 
          (alist = (* Generate non overlapping - Tabuloid *)
                Table[{{Mod[i, 3], Mod[i, 3] + .8}, 
                       {Mod[i, 4], Mod[i, 4] + .8},  
                       rndCol[]}, {i, nonOverlap}];
           blist = (* Random overlapping *)
                Table[{{a = rnR[3], a + rnR[2]}, {b = rnR[3], b + rnR[2]}, 
                      rndCol[]}, {Overlap}];
           Return[Join[alist, blist] (* Join both *)];)
      ];

主要

clist = genList[6, 4]; (* Generate a mix fixed & random set *)

incr = 0.05; (* may be some heuristics needed to determine best increment*)

clist = changeSize[clist,incr]; (* expand rects so that borders does not 
                                                         touch each other*)

(* Now remove all intercepting rectangles until no more intersections *)

workList = {}; (* the stack*)

While[findMaxIntesections[clist] > 0,          
                                      (*Iterate until no intersections *)
    clist    = sortListByIntersections[clist]; 
                                      (*Put the most intersected first*)
    PrependTo[workList, First[clist]];         
                                      (* Push workList with intersected *)
    clist    = Delete[clist, 1];      (* and Drop it from clist *)
];

(* There are no intersections now, lets pop the stack*)

While [workList != {},

    PrependTo[clist, First[workList]];       
                                 (*Push first element in front of clist*)
    workList = Delete[workList, 1];          
                                 (* and Drop it from worklist *)

    toMoveIndex = 1;                        
                                 (*Will move the most intersected Rect*)
    g = geometryCenter[clist];               
                                 (*so the geom. perception is preserved*)
    vectorToMove = rectCenter[clist, toMoveIndex] - g;
    If [Norm[vectorToMove] < 0.01, vectorToMove = {1,1}]; (*just in case*)  
    vectorToMove = vectorToMove/Norm[vectorToMove];      
                                            (*to manage step size wisely*)

    (*Now iterate finding minimum move first one way, then the other*)

    i = 1; (*movement quantity*)

    While[countIntersects[clist, toMoveIndex] != 0, 
                                           (*If the Rect still intersects*)
                                           (*move it alternating ways (-1)^n *)

      clist[[toMoveIndex]][[1]] += (-1)^i i incr vectorToMove[[1]];(*X coords*)
      clist[[toMoveIndex]][[2]] += (-1)^i i incr vectorToMove[[2]];(*Y coords*)

            i++;
    ];
];
clist = changeSize[clist, -incr](* restore original sizes*);

HTH!

编辑:多角度搜索

我对算法进行了更改,允许在所有方向上进行搜索,但优先考虑几何对称性强加的轴。
以更多周期为代价,这导致了更紧凑的最终配置,如下所示:

更多示例here

主循环的伪代码改为:

Expand each rectangle size by a few points to get gaps in final configuration
While There are intersections
    sort list of rectangles by number of intersections
    push most intersected rectangle on stack, and remove it from list
// Now all remaining rectangles doesn't intersect each other
While stack not empty
    find the geometric center G of the chart (each time!)
    find the PREFERRED movement vector M (from G to rectangle center)
    pop  rectangle from stack 
    With the rectangle
         While there are intersections (list+rectangle)
              For increasing movement modulus
                 For increasing angle (0, Pi/4)
                    rotate vector M expanding the angle alongside M
                    (* angle, -angle, Pi + angle, Pi-angle*)
                    re-position the rectangle accorging to M
    Re-insert modified vector into list
Shrink the rectangles to its original size

为了简洁起见,我不包括源代码,但如果您认为可以使用它,请询问它。我认为,如果你这样做,最好切换到 R-trees(这里需要大量的间隔测试)

【讨论】:

  • 不错的一个。我和我的朋友正在尝试实施它。 交叉手指感谢您花时间提出这个问题!
  • 解释思维过程、算法概念、难点和局限,并提供代码== +1。如果我能提供的话,还有更多。
  • @belisarlus 写得好!你有没有公开你的来源?
  • 这里还有其他答案试图以 java 方式回答这个问题。有没有人成功地将这个mathematica 解决方案移植到java 中?
【解决方案2】:

这是一个猜测。

找到矩形边界框的中心 C。

对于每个与另一个重叠的矩形 R。

  1. 定义运动矢量 v.
  2. 找出所有与 R 重叠的矩形 R'。
  3. 向 v 添加一个与 R 和 R' 中心之间的向量成比例的向量。
  4. 向 v 添加一个与 C 和 R 中心之间的向量成比例的向量。
  5. 将 R 移到 v.
  6. 重复直到没有重叠。

这会逐渐将矩形彼此移开,并远离所有矩形的中心。这将终止,因为来自第 4 步的 v 的组件最终会自行将它们分散得足够多。

【讨论】:

  • 好主意找到中心并围绕它移动矩形。 +1 唯一的问题是,找到中心本身就是另一个问题,而且对于您添加的每个矩形而言,这可能更具挑战性。
  • 找到中心很容易。只需取所有矩形角的最小值和最大值。而且你只做一次,而不是每次迭代一次。
  • 这也导致了最小的移动,因为如果没有任何东西与矩形重叠,它不会移动矩形。哦,第 4 步可以,所以如果没有重叠,你应该跳过第 4 步。找到需要最少移动的实际安排可能要困难得多。
  • 对于位于可见区域一角的两个矩形,算法应该能够理解图形是应该扩大还是缩小。就是吐槽。 (我知道可见性还没有在范围内,但我想重要的是不要通过仅仅扩展图形来解决问题,因为如果不是解决方案是微不足道的:取两个最近的正方形并“照射”所有图形从它的质心足以将这两个矩形分开)。当然,您的方法比这更好。我只是说我们不应该扩大,除非有必要。
  • @belisarius 如果没有必要,它不会展开。一旦没有任何东西与您的矩形重叠,它就会停止移动。 (它可能会重新开始,但仅在需要时。)如果有足够多的矩形或足够大的矩形,可能无法在屏幕上以全尺寸显示它们。在这种情况下,很容易找到重新调整空间的解决方案的边界框,并将所有内容缩放相同的数量,以便它们适合屏幕。
【解决方案3】:

我认为这个解决方案与 cape1232 给出的解决方案非常相似,但它已经实现了,所以值得一试:)

关注这个 reddit 讨论:http://www.reddit.com/r/gamedev/comments/1dlwc4/procedural_dungeon_generation_algorithm_explained/ 并查看描述和实现。没有可用的源代码,所以这是我在 AS3 中解决这个问题的方法(工作原理完全相同,但保持矩形对齐到网格的分辨率):

public class RoomSeparator extends AbstractAction {
    public function RoomSeparator(name:String = "Room Separator") {
        super(name);
    }

    override public function get finished():Boolean { return _step == 1; }

    override public function step():void {
        const repelDecayCoefficient:Number = 1.0;

        _step = 1;

        var count:int = _activeRoomContainer.children.length;
        for(var i:int = 0; i < count; i++) {
            var room:Room           = _activeRoomContainer.children[i];
            var center:Vector3D     = new Vector3D(room.x + room.width / 2, room.y + room.height / 2);
            var velocity:Vector3D   = new Vector3D();

            for(var j:int = 0; j < count; j++) {
                if(i == j)
                    continue;

                var otherRoom:Room = _activeRoomContainer.children[j];
                var intersection:Rectangle = GeomUtil.rectangleIntersection(room.createRectangle(), otherRoom.createRectangle());

                if(intersection == null || intersection.width == 0 || intersection.height == 0)
                    continue;

                var otherCenter:Vector3D = new Vector3D(otherRoom.x + otherRoom.width / 2, otherRoom.y + otherRoom.height / 2);
                var diff:Vector3D = center.subtract(otherCenter);

                if(diff.length > 0) {
                    var scale:Number = repelDecayCoefficient / diff.lengthSquared;
                    diff.normalize();
                    diff.scaleBy(scale);

                    velocity = velocity.add(diff);
                }
            }

            if(velocity.length > 0) {
                _step = 0;
                velocity.normalize();

                room.x += Math.abs(velocity.x) < 0.5 ? 0 : velocity.x > 0 ? _resolution : -_resolution;
                room.y += Math.abs(velocity.y) < 0.5 ? 0 : velocity.y > 0 ? _resolution : -_resolution;
            }
        }
    }
}

【讨论】:

  • 逻辑有缺陷。对于一个房间,velocity 是它的中心和其他房间的中心之间的向量之和,如果所有房间都以相同的中心堆叠,则所有房间的velocity.length == 0 都不会移动。同样,如果两个或多个房间有同一个中心的同一个矩形,它们会一起移动,但会保持堆叠。
【解决方案4】:

我真的很喜欢 b005t3r 的实现!它适用于我的测试用例,但是我的代表太低,无法对 2 个建议的修复发表评论。

  1. 您不应该以单个分辨率增量来平移房间,而应该以您刚刚痛苦计算的速度来平移!这使得分离更加有机,因为深度相交的房间在每次迭代中比不那么深的相交房间分开更多。

  2. 您不应该假设速度小于 0.5 意味着房间是分开的,因为您可能会在从未分开的情况下陷入困境。想象一下 2 个房间相交,但无法自行纠正,因为每当其中任何一个尝试纠正穿透时,他们都会将所需的速度计算为

这是一个 Java 解决方案(:干杯!

do {
    _separated = true;

    for (Room room : getRooms()) {
        // reset for iteration
        Vector2 velocity = new Vector2();
        Vector2 center = room.createCenter();

        for (Room other_room : getRooms()) {
            if (room == other_room)
                continue;

            if (!room.createRectangle().overlaps(other_room.createRectangle()))
                continue;

            Vector2 other_center = other_room.createCenter();
            Vector2 diff = new Vector2(center.x - other_center.x, center.y - other_center.y);
            float diff_len2 = diff.len2();

            if (diff_len2 > 0f) {
                final float repelDecayCoefficient = 1.0f;
                float scale = repelDecayCoefficient / diff_len2;
                diff.nor();
                diff.scl(scale);

                velocity.add(diff);
            }
        }

        if (velocity.len2() > 0f) {
            _separated = false;

            velocity.nor().scl(delta * 20f);

            room.getPosition().add(velocity);
        }
    }
} while (!_separated);

【讨论】:

    【解决方案5】:

    这是一个使用 Java 编写的算法,用于处理一组未旋转的Rectangles。它允许您指定所需的布局纵横比,并使用参数化的Rectangle 作为锚点定位集群,所有翻译都围绕该锚点进行。您还可以指定任意数量的填充,以将 Rectangles 散布到其中。

    public final class BoxxyDistribution {
    
    /* Static Definitions. */
    private static final int INDEX_BOUNDS_MINIMUM_X = 0;
    private static final int INDEX_BOUNDS_MINIMUM_Y = 1;
    private static final int INDEX_BOUNDS_MAXIMUM_X = 2;
    private static final int INDEX_BOUNDS_MAXIMUM_Y = 3;
    
    private static final double onCalculateMagnitude(final double pDeltaX, final double pDeltaY) {
        return Math.sqrt((pDeltaX * pDeltaX) + (pDeltaY + pDeltaY));
    }
    
    /* Updates the members of EnclosingBounds to ensure the dimensions of T can be completely encapsulated. */
    private static final void onEncapsulateBounds(final double[] pEnclosingBounds, final double pMinimumX, final double pMinimumY, final double pMaximumX, final double pMaximumY) {
        pEnclosingBounds[0] = Math.min(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X], pMinimumX);
        pEnclosingBounds[1] = Math.min(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], pMinimumY);
        pEnclosingBounds[2] = Math.max(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X], pMaximumX);
        pEnclosingBounds[3] = Math.max(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y], pMaximumY);
    }
    
    private static final void onEncapsulateBounds(final double[] pEnclosingBounds, final double[] pBounds) {
        BoxxyDistribution.onEncapsulateBounds(pEnclosingBounds, pBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X], pBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], pBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X], pBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y]);
    }
    
    private static final double onCalculateMidpoint(final double pMaximum, final double pMinimum) {
        return ((pMaximum - pMinimum) * 0.5) + pMinimum;
    }
    
    /* Re-arranges a List of Rectangles into something aesthetically pleasing. */
    public static final void onBoxxyDistribution(final List<Rectangle> pRectangles, final Rectangle pAnchor, final double pPadding, final double pAspectRatio, final float pRowFillPercentage) {
        /* Create a safe clone of the Rectangles that we can modify as we please. */
        final List<Rectangle> lRectangles  = new ArrayList<Rectangle>(pRectangles);
        /* Allocate a List to track the bounds of each Row. */
        final List<double[]>  lRowBounds   = new ArrayList<double[]>(); // (MinX, MinY, MaxX, MaxY)
        /* Ensure Rectangles does not contain the Anchor. */
        lRectangles.remove(pAnchor);
        /* Order the Rectangles via their proximity to the Anchor. */
        Collections.sort(pRectangles, new Comparator<Rectangle>(){ @Override public final int compare(final Rectangle pT0, final Rectangle pT1) {
            /* Calculate the Distance for pT0. */
            final double lDistance0 = BoxxyDistribution.onCalculateMagnitude(pAnchor.getCenterX() - pT0.getCenterX(), pAnchor.getCenterY() - pT0.getCenterY());
            final double lDistance1 = BoxxyDistribution.onCalculateMagnitude(pAnchor.getCenterX() - pT1.getCenterX(), pAnchor.getCenterY() - pT1.getCenterY());
            /* Compare the magnitude in distance between the anchor and the Rectangles. */
            return Double.compare(lDistance0, lDistance1);
        } });
        /* Initialize the RowBounds using the Anchor. */ /** TODO: Probably better to call getBounds() here. **/
        lRowBounds.add(new double[]{ pAnchor.getX(), pAnchor.getY(), pAnchor.getX() + pAnchor.getWidth(), pAnchor.getY() + pAnchor.getHeight() });
    
        /* Allocate a variable for tracking the TotalBounds of all rows. */
        final double[] lTotalBounds = new double[]{ Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY };
        /* Now we iterate the Rectangles to place them optimally about the Anchor. */
        for(int i = 0; i < lRectangles.size(); i++) {
            /* Fetch the Rectangle. */
            final Rectangle lRectangle = lRectangles.get(i);
            /* Iterate through each Row. */
            for(final double[] lBounds : lRowBounds) {
                /* Update the TotalBounds. */
                BoxxyDistribution.onEncapsulateBounds(lTotalBounds, lBounds);
            }
            /* Allocate a variable to state whether the Rectangle has been allocated a suitable RowBounds. */
            boolean lIsBounded = false;
            /* Calculate the AspectRatio. */
            final double lAspectRatio = (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] - lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X]) / (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y] - lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y]);
            /* We will now iterate through each of the available Rows to determine if a Rectangle can be stored. */
            for(int j = 0; j < lRowBounds.size() && !lIsBounded; j++) {
                /* Fetch the Bounds. */
                final double[] lBounds = lRowBounds.get(j);
                /* Calculate the width and height of the Bounds. */
                final double   lWidth  = lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] - lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X];
                final double   lHeight = lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y] - lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y];
                /* Determine whether the Rectangle is suitable to fit in the RowBounds. */
                if(lRectangle.getHeight() <= lHeight && !(lAspectRatio > pAspectRatio && lWidth > pRowFillPercentage * (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] - lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X]))) {
                    /* Register that the Rectangle IsBounded. */
                    lIsBounded = true;
                    /* Update the Rectangle's X and Y Co-ordinates. */
                    lRectangle.setFrame((lRectangle.getX() > BoxxyDistribution.onCalculateMidpoint(lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X], lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X])) ? lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] + pPadding : lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X] - (pPadding + lRectangle.getWidth()), lBounds[1], lRectangle.getWidth(), lRectangle.getHeight());
                    /* Update the Bounds. (Do not modify the vertical metrics.) */
                    BoxxyDistribution.onEncapsulateBounds(lTotalBounds, lRectangle.getX(), lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], lRectangle.getX() + lRectangle.getWidth(), lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y] + lHeight);
                }
            }
            /* Determine if the Rectangle has not been allocated a Row. */
            if(!lIsBounded) {
                /* Calculate the MidPoint of the TotalBounds. */
                final double lCentreY   = BoxxyDistribution.onCalculateMidpoint(lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y], lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y]);
                /* Determine whether to place the bounds above or below? */
                final double lYPosition = lRectangle.getY() < lCentreY ? lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y] - (pPadding + lRectangle.getHeight()) : (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y] + pPadding);
                /* Create a new RowBounds. */
                final double[] lBounds  = new double[]{ pAnchor.getX(), lYPosition, pAnchor.getX() + lRectangle.getWidth(), lYPosition + lRectangle.getHeight() };
                /* Allocate a new row, roughly positioned about the anchor. */
                lRowBounds.add(lBounds);
                /* Position the Rectangle. */
                lRectangle.setFrame(lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X], lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], lRectangle.getWidth(), lRectangle.getHeight());
            }
        }
    }
    

    }

    这是一个使用1.2AspectRatio0.8FillPercentage10.0Padding 的示例。

    这是一种确定性方法,允许在锚点周围产生间距,同时保持锚点本身的位置不变。这允许布局发生在用户兴趣点所在的任何地方。选择位置的逻辑非常简单,但我认为根据元素的初始位置对元素进行排序然后迭代它们的周围架构是实现相对可预测的分布的有用方法。另外,我们不依赖迭代交叉测试或类似的东西,只是建立一些边界框来给我们一个广泛的指示来对齐事物的位置;在此之后,应用填充就很自然了。

    【讨论】:

      【解决方案6】:

      这是一个采用 cape1232 答案的版本,是 Java 的独立可运行示例:

      public class Rectangles extends JPanel {
      
          List<Rectangle2D> rectangles = new ArrayList<Rectangle2D>();
          {
              // x,y,w,h
              rectangles.add(new Rectangle2D.Float(300, 50, 50, 50));
      
              rectangles.add(new Rectangle2D.Float(300, 50, 20, 50));
      
              rectangles.add(new Rectangle2D.Float(100, 100, 100, 50));
      
              rectangles.add(new Rectangle2D.Float(120, 200, 50, 50));
      
              rectangles.add(new Rectangle2D.Float(150, 130, 100, 100));
      
              rectangles.add(new Rectangle2D.Float(0, 100, 100, 50));
      
              for (int i = 0; i < 10; i++) {
                  for (int j = 0; j < 10; j++) {
                      rectangles.add(new Rectangle2D.Float(i * 40, j * 40, 20, 20));
                  }
              }
          }
      
          List<Rectangle2D> rectanglesToDraw;
      
          protected void reset() {
              rectanglesToDraw = rectangles;
      
              this.repaint();
          }
      
          private List<Rectangle2D> findIntersections(Rectangle2D rect, List<Rectangle2D> rectList) {
      
              ArrayList<Rectangle2D> intersections = new ArrayList<Rectangle2D>();
      
              for (Rectangle2D intersectingRect : rectList) {
                  if (!rect.equals(intersectingRect) && intersectingRect.intersects(rect)) {
                      intersections.add(intersectingRect);
                  }
              }
      
              return intersections;
          }
      
          protected void fix() {
              rectanglesToDraw = new ArrayList<Rectangle2D>();
      
              for (Rectangle2D rect : rectangles) {
                  Rectangle2D copyRect = new Rectangle2D.Double();
                  copyRect.setRect(rect);
                  rectanglesToDraw.add(copyRect);
              }
      
              // Find the center C of the bounding box of your rectangles.
              Rectangle2D surroundRect = surroundingRect(rectanglesToDraw);
              Point center = new Point((int) surroundRect.getCenterX(), (int) surroundRect.getCenterY());
      
              int movementFactor = 5;
      
              boolean hasIntersections = true;
      
              while (hasIntersections) {
      
                  hasIntersections = false;
      
                  for (Rectangle2D rect : rectanglesToDraw) {
      
                      // Find all the rectangles R' that overlap R.
                      List<Rectangle2D> intersectingRects = findIntersections(rect, rectanglesToDraw);
      
                      if (intersectingRects.size() > 0) {
      
                          // Define a movement vector v.
                          Point movementVector = new Point(0, 0);
      
                          Point centerR = new Point((int) rect.getCenterX(), (int) rect.getCenterY());
      
                          // For each rectangle R that overlaps another.
                          for (Rectangle2D rPrime : intersectingRects) {
                              Point centerRPrime = new Point((int) rPrime.getCenterX(), (int) rPrime.getCenterY());
      
                              int xTrans = (int) (centerR.getX() - centerRPrime.getX());
                              int yTrans = (int) (centerR.getY() - centerRPrime.getY());
      
                              // Add a vector to v proportional to the vector between the center of R and R'.
                              movementVector.translate(xTrans < 0 ? -movementFactor : movementFactor,
                                      yTrans < 0 ? -movementFactor : movementFactor);
      
                          }
      
                          int xTrans = (int) (centerR.getX() - center.getX());
                          int yTrans = (int) (centerR.getY() - center.getY());
      
                          // Add a vector to v proportional to the vector between C and the center of R.
                          movementVector.translate(xTrans < 0 ? -movementFactor : movementFactor,
                                  yTrans < 0 ? -movementFactor : movementFactor);
      
                          // Move R by v.
                          rect.setRect(rect.getX() + movementVector.getX(), rect.getY() + movementVector.getY(),
                                  rect.getWidth(), rect.getHeight());
      
                          // Repeat until nothing overlaps.
                          hasIntersections = true;
                      }
      
                  }
              }
              this.repaint();
          }
      
          private Rectangle2D surroundingRect(List<Rectangle2D> rectangles) {
      
              Point topLeft = null;
              Point bottomRight = null;
      
              for (Rectangle2D rect : rectangles) {
                  if (topLeft == null) {
                      topLeft = new Point((int) rect.getMinX(), (int) rect.getMinY());
                  } else {
                      if (rect.getMinX() < topLeft.getX()) {
                          topLeft.setLocation((int) rect.getMinX(), topLeft.getY());
                      }
      
                      if (rect.getMinY() < topLeft.getY()) {
                          topLeft.setLocation(topLeft.getX(), (int) rect.getMinY());
                      }
                  }
      
                  if (bottomRight == null) {
                      bottomRight = new Point((int) rect.getMaxX(), (int) rect.getMaxY());
                  } else {
                      if (rect.getMaxX() > bottomRight.getX()) {
                          bottomRight.setLocation((int) rect.getMaxX(), bottomRight.getY());
                      }
      
                      if (rect.getMaxY() > bottomRight.getY()) {
                          bottomRight.setLocation(bottomRight.getX(), (int) rect.getMaxY());
                      }
                  }
              }
      
              return new Rectangle2D.Double(topLeft.getX(), topLeft.getY(), bottomRight.getX() - topLeft.getX(),
                      bottomRight.getY() - topLeft.getY());
          }
      
          public void paintComponent(Graphics g) {
              super.paintComponent(g);
              Graphics2D g2d = (Graphics2D) g;
      
              for (Rectangle2D entry : rectanglesToDraw) {
                  g2d.setStroke(new BasicStroke(1));
                  // g2d.fillRect((int) entry.getX(), (int) entry.getY(), (int) entry.getWidth(),
                  // (int) entry.getHeight());
                  g2d.draw(entry);
              }
      
          }
      
          protected static void createAndShowGUI() {
              Rectangles rects = new Rectangles();
      
              rects.reset();
      
              JFrame frame = new JFrame("Rectangles");
              frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              frame.setLayout(new BorderLayout());
              frame.add(rects, BorderLayout.CENTER);
      
              JPanel buttonsPanel = new JPanel();
      
              JButton fix = new JButton("Fix");
      
              fix.addActionListener(new ActionListener() {
      
                  @Override
                  public void actionPerformed(ActionEvent e) {
                      rects.fix();
      
                  }
              });
      
              JButton resetButton = new JButton("Reset");
      
              resetButton.addActionListener(new ActionListener() {
      
                  @Override
                  public void actionPerformed(ActionEvent e) {
                      rects.reset();
                  }
              });
      
              buttonsPanel.add(fix);
              buttonsPanel.add(resetButton);
      
              frame.add(buttonsPanel, BorderLayout.SOUTH);
      
              frame.setSize(400, 400);
              frame.setLocationRelativeTo(null);
              frame.setVisible(true);
          }
      
          public static void main(String[] args) {
              SwingUtilities.invokeLater(new Runnable() {
      
                  @Override
                  public void run() {
                      createAndShowGUI();
      
                  }
              });
          }
      
      }
      

      【讨论】:

        猜你喜欢
        • 2017-03-14
        • 1970-01-01
        • 2013-03-01
        • 1970-01-01
        • 2018-11-02
        • 1970-01-01
        • 2012-06-21
        • 2023-03-18
        • 1970-01-01
        相关资源
        最近更新 更多