Một thuật toán để xóa các hình chữ nhật chồng lên nhau?


92

Vấn đề này thực sự giải quyết vấn đề cuộn qua, tôi sẽ chỉ khái quát bên dưới như vậy:

Tôi có chế độ xem 2D và tôi có một số hình chữ nhật trong một khu vực trên màn hình. Làm cách nào để trải các hộp đó ra sao cho chúng không chồng lên nhau mà chỉ điều chỉnh chúng với sự di chuyển tối thiểu?

Vị trí của các hình chữ nhật là động và phụ thuộc vào đầu vào của người dùng, vì vậy vị trí của chúng có thể ở bất kỳ đâu.

văn bản thay thếHình ảnh đính kèm cho thấy vấn đề và giải pháp mong muốn

Thực ra, vấn đề của cuộc sống thực là liên quan đến việc di chuyển.

Câu trả lời cho các câu hỏi trong phần bình luận

  1. Kích thước của hình chữ nhật không cố định và phụ thuộc vào độ dài của văn bản trong cuộn qua

  2. Về kích thước màn hình, ngay bây giờ tôi nghĩ tốt hơn nên cho rằng kích thước của màn hình là đủ cho các hình chữ nhật. Nếu có quá nhiều hình chữ nhật và thuật ngữ không tạo ra giải pháp, thì tôi chỉ cần điều chỉnh nội dung.

  3. Yêu cầu 'di chuyển tối thiểu' là về tính thẩm mỹ hơn là yêu cầu về kỹ thuật tuyệt đối. Người ta có thể tạo khoảng trống cho hai hình chữ nhật bằng cách thêm một khoảng cách lớn giữa chúng, nhưng nó sẽ không đẹp như một phần của GUI. Ý tưởng là để cuộn qua / hình chữ nhật gần với nguồn của nó (sau đó tôi sẽ kết nối với nguồn bằng một đường màu đen). Vì vậy, hoặc 'di chuyển chỉ một đối với x' hoặc 'di chuyển cả hai đối với nửa x' đều được.


2
Chúng ta có thể giả sử các hình chữ nhật luôn được định hướng theo chiều ngang hoặc chiều dọc, và không nghiêng trên trục của chúng một góc không?
Matt,

2
Có, giả định là hợp lệ.
Extrakun

Chúng ta có thể giả định rằng màn hình luôn đủ lớn để hỗ trợ các hình chữ nhật không bị chồng lên nhau không? Các hình chữ nhật luôn có cùng kích thước? Bạn có thể nói rõ hơn về "di chuyển tối thiểu" nghĩa là gì không? Ví dụ: nếu bạn có 2 hình chữ nhật nằm chồng lên nhau thì tốt hơn là bạn chỉ nên chọn 1 trong số chúng một khoảng cách đầy đủ để loại bỏ phần chồng chéo hoặc di chuyển cả hai nửa khoảng cách?
Nick Larsen

@NickLarsen, tôi đã trả lời câu hỏi của bạn trong câu trả lời đã chỉnh sửa ở trên. Cảm ơn!
Extrakun

1
@joe: có lẽ anh ấy muốn hiểu giải pháp, vì vậy anh ấy có thể hỗ trợ nó.
Beska,

Câu trả lời:


95

Tôi đã làm việc một chút trong việc này, vì tôi cũng cần thứ gì đó tương tự, nhưng tôi đã trì hoãn việc phát triển thuật toán. Bạn đã giúp tôi có được một số xung lực: D

Tôi cũng cần mã nguồn, vì vậy nó đây. Tôi đã giải quyết nó trong Mathematica, nhưng vì tôi chưa sử dụng nhiều các tính năng chức năng, tôi đoán sẽ dễ dàng dịch sang bất kỳ ngôn ngữ thủ tục nào.

Một viễn cảnh lịch sử

Đầu tiên tôi quyết định phát triển thuật toán cho các vòng tròn, vì giao điểm dễ tính hơn. Nó chỉ phụ thuộc vào các tâm và bán kính.

Tôi đã có thể sử dụng công cụ giải phương trình Mathematica và nó hoạt động rất tốt.

Chỉ cần nhìn:

văn bản thay thế

Thật dễ dàng. Tôi vừa tải trình giải quyết vấn đề sau:

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
   ]

Đơn giản như vậy, và Mathematica đã làm tất cả công việc.

Tôi nói "Ha! Thật dễ dàng, bây giờ chúng ta hãy đi cho các hình chữ nhật!". Nhưng tôi đã nhầm ...

Blues hình chữ nhật

Vấn đề chính với các hình chữ nhật là truy vấn giao điểm là một hàm khó chịu. Cái gì đó như:

Vì vậy, khi tôi cố gắng bổ sung Mathematica với rất nhiều điều kiện này cho phương trình, nó hoạt động tệ đến mức tôi quyết định làm một cái gì đó theo thủ tục.

Thuật toán của tôi kết thúc như sau:

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

Bạn có thể lưu ý rằng điều kiện "chuyển động nhỏ nhất" không được thỏa mãn hoàn toàn (chỉ theo một hướng). Nhưng tôi nhận thấy rằng việc di chuyển các hình chữ nhật theo bất kỳ hướng nào để đáp ứng nó, đôi khi kết thúc bằng việc thay đổi bản đồ gây nhầm lẫn cho người dùng.

Khi tôi đang thiết kế giao diện người dùng, tôi chọn di chuyển hình chữ nhật ra xa hơn một chút, nhưng theo cách dễ đoán hơn. Bạn có thể thay đổi thuật toán để kiểm tra tất cả các góc và tất cả các bán kính xung quanh vị trí hiện tại của nó cho đến khi tìm thấy một vị trí trống, mặc dù nó sẽ đòi hỏi nhiều hơn.

Dù sao, đây là những ví dụ về kết quả (trước / sau):

văn bản thay thế

Chỉnh sửa> Các ví dụ khác tại đây

Như bạn có thể thấy, "chuyển động tối thiểu" không được thỏa mãn, nhưng kết quả là đủ tốt.

Tôi sẽ đăng mã ở đây vì tôi đang gặp một số rắc rối với kho lưu trữ SVN của mình. Tôi sẽ xóa nó khi vấn đề được giải quyết.

Biên tập:

Bạn cũng có thể sử dụng R-Trees để tìm các giao điểm hình chữ nhật, nhưng có vẻ quá mức cần thiết để xử lý một số lượng nhỏ hình chữ nhật. Và tôi chưa triển khai các thuật toán. Có lẽ ai đó khác có thể chỉ cho bạn một triển khai hiện có trên nền tảng bạn chọn.

Cảnh báo! Mã là cách tiếp cận đầu tiên .. chất lượng chưa tuyệt vời và chắc chắn có một số lỗi.

Đó là 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 *)];)
      ];

Chủ yếu

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!

Chỉnh sửa: Tìm kiếm nhiều góc độ

Tôi đã thực hiện một thay đổi trong thuật toán cho phép tìm kiếm theo mọi hướng, nhưng ưu tiên cho trục do đối xứng hình học áp đặt.
Với chi phí của nhiều chu kỳ hơn, điều này dẫn đến cấu hình cuối cùng nhỏ gọn hơn, như bạn có thể thấy ở đây bên dưới:

nhập mô tả hình ảnh ở đây

Nhiều mẫu hơn ở đây .

Mã giả cho vòng lặp chính đã thay đổi thành:

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

Tôi không bao gồm mã nguồn cho ngắn gọn, nhưng chỉ cần yêu cầu nó nếu bạn nghĩ rằng bạn có thể sử dụng nó. Tôi nghĩ rằng, nếu bạn đi theo hướng này, tốt hơn nên chuyển sang cây R (rất nhiều bài kiểm tra ngắt quãng cần thiết ở đây)


4
Tốt lắm. Bạn tôi và tôi đang cố gắng thực hiện nó. chéo ngón tay Cảm ơn đã dành thời gian để đưa ra điều này!
Extrakun

9
Giải thích quy trình suy nghĩ, khái niệm thuật toán, khó khăn & hạn chế và cung cấp mã == +1. Và nhiều hơn nữa nếu tôi có thể cung cấp nó.
Beska

1
@belisarlus Viết hay lắm! Bạn đã bao giờ công khai nguồn của mình chưa?
Rohan West

Có những câu trả lời khác ở đây cố gắng trả lời điều này theo cách java. Có ai đã chuyển thành công giải pháp toán học này sang java?
mainringargs 21/09/17

11

Đây là một phỏng đoán.

Tìm tâm C của hộp giới hạn các hình chữ nhật của bạn.

Đối với mỗi hình chữ nhật R chồng lên nhau.

  1. Xác định một vectơ chuyển động v.
  2. Tìm tất cả các hình chữ nhật R 'trùng với R.
  3. Thêm một vectơ với v tỉ lệ với vectơ giữa tâm của R và R '.
  4. Thêm một vectơ với v tỉ lệ với vectơ giữa C và tâm của R.
  5. Di chuyển R theo v.
  6. Lặp lại cho đến khi không có gì trùng lặp.

Điều này dần dần di chuyển các hình chữ nhật ra xa nhau và tâm của tất cả các hình chữ nhật. Điều này sẽ kết thúc bởi vì thành phần của v từ bước 4 cuối cùng sẽ tự trải rộng chúng ra.


Ý tưởng hay là tìm tâm và di chuyển các hình chữ nhật về nó. +1 Vấn đề duy nhất là việc tự tìm tâm là một vấn đề khác và một vấn đề có thể khó hơn nhiều đối với mỗi hình chữ nhật mà bạn thêm vào.
Nick Larsen

2
Tìm trung tâm rất dễ dàng. Chỉ cần lấy giá trị tối thiểu và tối đa của các góc của tất cả các hình chữ nhật. Và bạn chỉ làm điều đó một lần, không phải một lần mỗi lần lặp lại.
cape1232

Điều này dẫn đến di chuyển tối thiểu, theo nghĩa là nó không di chuyển một hình chữ nhật nếu không có gì chồng lên nó. Ồ, bước 4 có, vì vậy bạn nên bỏ qua bước 4 nếu không có sự trùng lặp. Việc tìm kiếm sự sắp xếp thực tế yêu cầu chuyển động tối thiểu có lẽ khó hơn nhiều.
cape1232

Đối với hai hình chữ nhật nằm ở một góc của vùng nhìn thấy, alg sẽ có thể hiểu được đồ thị nên được mở rộng hay thu nhỏ. Chỉ cần chạy đua. (Tôi biết rằng khả năng hiển thị vẫn chưa nằm trong phạm vi, nhưng tôi đoán điều quan trọng là không giải quyết vấn đề bằng cách chỉ mở rộng biểu đồ là đủ, bởi vì nếu không giải pháp là tầm thường: lấy hai hình vuông gần nhất và "chiếu xạ" tất cả đồ thị từ khối tâm của nó đủ để hai hình chữ nhật đó rời nhau). Tất nhiên, cách tiếp cận của bạn tốt hơn cách này. Tôi chỉ nói rằng chúng ta không nên mở rộng trừ khi nó cần thiết.
Tiến sĩ belisarius

@belisarius Điều này không mở rộng nếu nó không cần thiết. Khi không có gì chồng lên hình chữ nhật của bạn, nó sẽ ngừng di chuyển. (Nó có thể bắt đầu lại, nhưng chỉ khi cần.) Với đủ hình chữ nhật hoặc hình đủ lớn, có thể không hiển thị tất cả chúng trên màn hình ở kích thước đầy đủ. Trong trường hợp đó, có thể dễ dàng tìm thấy hộp giới hạn của giải pháp được thay thế và chia tỷ lệ mọi thứ bằng nhau để chúng vừa với màn hình.
cape1232

6

Tôi nghĩ rằng giải pháp này khá giống với giải pháp được đưa ra bởi cape1232, nhưng nó đã được triển khai, vì vậy đáng để thử :)

Theo dõi cuộc thảo luận reddit này: http://www.reddit.com/r/gamedev/comments/1dlwc4/procedural_dungeon_generation_algorithm_explained/ và xem mô tả và cách triển khai. Không có mã nguồn nào có sẵn, vì vậy đây là cách tiếp cận của tôi đối với vấn đề này trong AS3 (hoạt động hoàn toàn giống nhau, nhưng giữ cho các hình chữ nhật được gắn với độ phân giải của lưới):

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

Có một lỗ hổng trong logic. Đối với một phòng, velocitylà tổng các vectơ giữa tâm của nó và tâm của các phòng khác, nếu tất cả các phòng được xếp chồng lên nhau với cùng một tâm, velocity.length == 0cho tất cả các phòng và không có gì sẽ di chuyển. Theo cách tương tự, nếu hai hoặc nhiều phòng có cùng hình chữ nhật với cùng tâm, chúng sẽ di chuyển cùng nhau nhưng vẫn xếp chồng lên nhau.
Peyre

6

Tôi thực sự thích cách triển khai của b005t3r! Nó hoạt động trong các trường hợp thử nghiệm của tôi, tuy nhiên đại diện của tôi quá thấp để có thể để lại nhận xét với 2 bản sửa lỗi được đề xuất.

  1. Bạn không nên dịch các phòng theo từng độ phân giải đơn lẻ, bạn nên dịch theo tốc độ mà bạn vừa đau đầu tính toán! Điều này làm cho sự tách biệt hữu cơ hơn vì các phòng giao nhau sâu sẽ tách biệt nhiều hơn mỗi lần lặp lại so với các phòng giao nhau không quá sâu.

  2. Bạn không nên cho rằng vận tốc nhỏ hơn 0,5 có nghĩa là các phòng tách biệt nhau vì bạn có thể gặp khó khăn trong trường hợp không bao giờ tách rời nhau. Hãy tưởng tượng 2 phòng giao nhau, nhưng không thể tự điều chỉnh vì bất cứ khi nào một trong hai cố gắng sửa độ xuyên thấu, họ sẽ tính toán vận tốc yêu cầu là <0,5 để chúng lặp lại liên tục.

Đây là một giải pháp 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

Đây là một thuật toán được viết bằng Java để xử lý một nhóm các Rectangles chưa được giải mã. Nó cho phép bạn chỉ định tỷ lệ co mong muốn của bố cục và định vị cụm bằng cách sử dụng một tham số được tham số hóa Rectanglelàm điểm neo, mà tất cả các bản dịch được thực hiện đều được định hướng. Bạn cũng có thể chỉ định một lượng đệm tùy ý mà bạn muốn trải Rectanglechúng theo.

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

}

Dưới đây là một ví dụ sử dụng AspectRatiocủa 1.2, a FillPercentagecủa 0.8và a Paddingcủa 10.0.

100 hình chữ nhật được chia tỷ lệ và phân phối ngẫu nhiên.

100 hình chữ nhật ngẫu nhiên được phân phối bằng cách sử dụng BoxxyDistribution.

Đây là một cách tiếp cận xác định cho phép khoảng cách xảy ra xung quanh mỏ neo trong khi vẫn giữ nguyên vị trí của mỏ neo. Điều này cho phép bố cục diễn ra xung quanh bất cứ nơi nào Điểm ưa thích của người dùng. Logic để chọn một vị trí khá đơn giản, nhưng tôi nghĩ rằng kiến ​​trúc xung quanh của việc sắp xếp các phần tử dựa trên vị trí ban đầu của chúng và sau đó lặp lại chúng là một cách tiếp cận hữu ích để thực hiện một phân phối tương đối có thể dự đoán được. Thêm vào đó, chúng tôi không dựa vào các bài kiểm tra nút giao lặp đi lặp lại hoặc bất cứ điều gì tương tự, chỉ xây dựng một số hộp giới hạn để cung cấp cho chúng tôi dấu hiệu rộng rãi về vị trí sắp xếp mọi thứ; sau này, việc áp dụng đệm chỉ đến một cách tự nhiên.


3

Đây là một phiên bản có câu trả lời của cape1232 và là một ví dụ có thể chạy độc lập cho 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();

            }
        });
    }

}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.