겹치는 직사각형을 간격을 두는 알고리즘?


92

이 문제는 실제로 롤오버를 다룹니다. 다음과 같이 일반화하겠습니다.

2D보기가 있고 화면 영역 내에 여러 개의 직사각형이 있습니다. 상자가 서로 겹치지 않고 최소한의 움직임으로 만 조정하도록 상자를 펼치려면 어떻게해야합니까?

직사각형의 위치는 동적이며 사용자의 입력에 따라 달라 지므로 위치는 어디에나있을 수 있습니다.

첨부 된 대체 텍스트이미지는 문제와 원하는 솔루션을 보여줍니다.

실생활 문제는 실제로 롤오버를 다룹니다.

댓글의 질문에 대한 답변

  1. 직사각형의 크기는 고정되어 있지 않으며 롤오버의 텍스트 길이에 따라 다릅니다.

  2. 화면 크기에 관해서는 지금 당장은 화면 크기가 직사각형에 충분하다고 가정하는 것이 좋습니다. 직사각형이 너무 많고 알고리즘이 솔루션을 생성하지 않으면 내용을 조정하기 만하면됩니다.

  3. '최소로 이동'하기위한 요구 사항은 절대적인 엔지니어링 요구 사항보다 미학에 더 가깝습니다. 두 개의 직사각형 사이에 넓은 거리를 추가하여 두 개의 직사각형을 배치 할 수 있지만 GUI의 일부로보기에는 좋지 않습니다. 아이디어는 롤오버 / 사각형을 소스에 가깝게 만드는 것입니다 (그런 다음 검은 선으로 소스에 연결합니다). 따라서 'X를 위해 하나만 이동'또는 'X 절반을 위해 둘 다 이동'하는 것이 좋습니다.


2
직사각형이 항상 수평 또는 수직 방향이고 축에서 비스듬히 기울어지지 않는다고 가정 할 수 있습니까?
Matt

2
예, 가정이 유효합니다.
Extrakun 2010

화면이 항상 겹치지 않고 직사각형을 지원할만큼 충분히 크다고 가정 할 수 있습니까? 직사각형은 항상 같은 크기입니까? "최소한의 움직임"이 무엇을 의미하는지 좀 더 구체적으로 말씀해 주시겠습니까? 예를 들어, 2 개의 직사각형이 서로 똑바로 위에있는 경우 겹치는 부분을 제거하기 위해 1 개만 전체 거리를 두는 것이 더 낫습니까?
Nick Larsen

@NickLarsen, 위의 편집 된 답변에서 귀하의 질문에 답변했습니다. 감사!
Extrakun

1
@joe : 솔루션을 이해하고 싶어서 지원할 수 있습니다.
Beska

답변:


95

비슷한 것이 필요했기 때문에 이것에 대해 약간 작업했지만 알고리즘 개발을 지연 시켰습니다. 당신은 내가 약간의 충동을 얻도록 도와주었습니다 : 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가 모든 작업을 수행했습니다.

나는 "Ha! 쉽습니다. 이제 직사각형으로 가자!" 그러나 나는 틀렸다 ...

직사각형 블루스

직사각형의 주요 문제는 교차로를 쿼리하는 것이 불쾌한 기능이라는 것입니다. 다음과 같은 것 :

그래서 방정식에 대한 이러한 많은 조건을 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

"가장 작은 움직임"조건이 완전히 만족되지 않는다는 것을 알 수 있습니다 (한 방향으로 만). 하지만 사각형을 원하는 방향으로 움직이면 사용자에게 혼란스러운지도가 변경되는 경우가 있습니다.

사용자 인터페이스를 디자인 할 때 직사각형을 조금 더 이동하지만 더 예측 가능한 방식으로 이동합니다. 훨씬 더 까다로울 수 있지만 빈 장소가 발견 될 때까지 현재 위치를 둘러싼 모든 각도와 모든 반경을 검사하도록 알고리즘을 변경할 수 있습니다.

어쨌든 다음은 결과의 예입니다 (전 / 후).

대체 텍스트

편집> 여기에 더 많은 예

보시다시피 "최소 움직임"은 만족스럽지 않지만 결과는 충분합니다.

SVN 저장소에 문제가 있으므로 여기에 코드를 게시하겠습니다. 문제가 해결되면 제거하겠습니다.

편집하다:

R-Tree를 사용할 수도 있습니다.직사각형 교차점을 찾기 를 있지만 적은 수의 직사각형을 처리하는 것은 과잉으로 보입니다. 그리고 이미 구현 된 알고리즘이 없습니다. 다른 사람이 선택한 플랫폼의 기존 구현을 알려줄 수 있습니다.

경고! 코드는 첫 번째 접근 방식입니다. 아직 품질이 좋지는 않지만 확실히 버그가 있습니다.

Mathematica입니다.

(*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!

편집 : 다중 각도 검색

모든 방향으로 검색 할 수 있도록 알고리즘을 변경했지만 기하학적 대칭에 의해 부과 된 축을 선호했습니다.
더 많은 사이클을 희생시키면서 아래에서 볼 수 있듯이 최종 구성이 더 간결 해졌습니다.

여기에 이미지 설명 입력

여기에 더 많은 샘플이 있습니다 .

메인 루프의 의사 코드가 다음과 같이 변경되었습니다.

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- 트리로 전환하는 것이 더 낫다고 생각합니다 (여기에 많은 간격 테스트가 필요합니다).


4
멋지네요. 제 친구와 저는 그것을 구현하려고합니다. 십자 손가락 올려 주셔서 감사합니다!
Extrakun

9
사고 과정, 알고리즘 개념, 어려움 및 한계를 설명하고 코드 제공 == +1. 그리고 내가 그것을 제공 할 수 있다면 더.
Beska

1
@belisarlus 대단한 쓰기! 출처를 공개 한 적이 있습니까?
Rohan West

여기에 자바 방식으로 대답하려는 다른 답변이 있습니다. 이 mathematica 솔루션을 Java로 성공적으로 이식 한 사람이 있습니까?
mainstringargs

11

여기에 추측이 있습니다.

직사각형 경계 상자의 중심 C를 찾으십시오.

서로 겹치는 각 직사각형 R에 대해.

  1. 이동 벡터 v를 정의합니다.
  2. R과 겹치는 모든 직사각형 R '을 찾습니다.
  3. R 중심과 R '사이의 벡터에 비례하는 벡터를 v에 추가합니다.
  4. C와 R 중심 사이의 벡터에 비례하여 v에 벡터를 추가합니다.
  5. R을 v만큼 이동합니다.
  6. 겹치지 않을 때까지 반복하십시오.

이렇게하면 직사각형이 점차적으로 서로 떨어져 모든 직사각형의 중심으로 이동합니다. 4 단계의 v 구성 요소가 결국 자체적으로 충분히 분산되므로 종료됩니다.


중심을 찾고 그 주위로 직사각형을 이동하는 것이 좋습니다. +1 유일한 문제는 중심을 찾는 것이 자체적으로 또 다른 문제이며 추가하는 각 직사각형에 대해 훨씬 더 어려울 수 있다는 것입니다.
Nick Larsen

2
센터를 찾는 것은 쉽습니다. 모든 직사각형 모서리의 최소 및 최대를 취하십시오. 그리고 반복 당 한 번이 아니라 한 번만 수행합니다.
cape1232 2010

이로 인해 사각형이 겹치지 않으면 이동하지 않는다는 점에서 최소한의 이동이 발생합니다. 아, 4 단계는 그렇기 때문에 겹치는 부분이 없으면 4 단계를 건너 뛰어야합니다. 최소한의 움직임이 필요한 실제 배치를 찾는 것은 아마도 훨씬 더 어려울 것입니다.
cape1232 2010

가시 영역의 모서리에있는 두 개의 직사각형의 경우 alg는 그래프가 확장 또는 축소되어야하는지 이해할 수 있어야합니다. 그냥 외침. (나는 아직 스코프에 가시성이 없다는 것을 알고 있지만, 해결책이 사소하지 않다면 그래프를 충분히 확장하여 문제를 해결하지 않는 것이 중요하다고 생각합니다. 가장 가까운 두 개의 사각형을 취하고 모든 그래프를 "조사"합니다. 이 두 직사각형을 분리하기에 충분한 질량 중심에서). 물론 당신의 접근 방식은 이것보다 낫습니다. 꼭 필요한 경우가 아니면 확장하지 말아야한다는 것입니다.
박사 벨리 사리우스

@belisarius 필요하지 않은 경우 확장되지 않습니다. 직사각형과 겹치는 것이 없으면 움직이지 않습니다. (다시 시작할 수 있지만 필요한 경우에만 가능합니다.) 직사각형이 충분하거나 충분히 큰 경우 전체 크기로 화면에 모두 표시하지 못할 수 있습니다. 이 경우 공간이 조정 된 솔루션의 경계 상자를 쉽게 찾고 화면에 맞도록 모든 항목을 동일한 크기로 조정합니다.
cape1232 2010-07-17

6

이 솔루션은 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모든 객실에 대해 아무것도 옮기 없습니다. 같은 방식으로 두 개 이상의 방에 같은 중심과 같은 직사각형이 있으면 함께 이동하지만 스택 상태로 유지됩니다.
Peyre

6

나는 b005t3r의 구현을 정말 좋아합니다! 내 테스트 케이스에서 작동하지만 내 담당자가 너무 낮아 2 가지 제안 수정 사항에 대한 의견을 남길 수 없습니다.

  1. 단일 해상도 증분으로 방을 번역해서는 안되며, 방금 계산 한 고통의 속도로 번역해야합니다! 이렇게하면 깊이 교차 된 방이 너무 깊게 교차하지 않는 방보다 더 많은 반복이 분리되므로 분리가 더 유기적입니다.

  2. 벨로 시이트가 0.5보다 작다는 것은 방이 분리되어 있지 않다는 것을 의미한다고 가정해서는 안됩니다. 두 개의 방이 교차한다고 상상 해보면 어느 쪽이 침투를 수정하려고 할 때마다 필요한 속도를 0.5 미만으로 계산하여 끝없이 반복하기 때문에 스스로 수정할 수 없습니다.

다음은 Java 솔루션입니다 (: Cheers!

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

4

다음은 회전되지 않은 클러스터를 처리하기 위해 Java를 사용하여 작성된 알고리즘입니다 Rectangle. 레이아웃의 원하는 종횡비를 지정하고 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());
        }
    }
}

}

다음은 AspectRatioof 1.2, a FillPercentageof 0.8및 a Paddingof 를 사용하는 예 입니다 10.0.

무작위로 크기가 조정되고 분산 된 100 개의 직사각형.

BoxxyDistribution을 사용하여 분포 된 100 개의 임의 직사각형.

이것은 앵커 자체의 위치를 ​​변경하지 않고 그대로 두면서 앵커 주변에 간격을 두는 결정 론적 접근 방식입니다. 이를 통해 사용자의 관심 지점이있는 곳 어디에서나 레이아웃이 발생할 수 있습니다. 위치를 선택하는 논리는 매우 단순하지만 초기 위치를 기준으로 요소를 정렬 한 다음 반복하는 주변 아키텍처는 상대적으로 예측 가능한 분포를 구현하는 데 유용한 접근 방식이라고 생각합니다. 게다가 우리는 반복적 인 교차 테스트 나 그와 비슷한 것에 의존하지 않고 단지 몇 개의 경계 상자를 만들어서 정렬 할 위치에 대한 광범위한 표시를 제공합니다. 그 후에 패딩을 적용하는 것은 자연스럽게 이루어집니다.


3

다음은 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();

            }
        });
    }

}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.