Algoritme untuk mengurangi persegi panjang yang tumpang tindih?

93

Masalah ini sebenarnya berkaitan dengan roll-over, saya hanya akan menggeneralisasikan di bawah ini seperti:

Saya memiliki tampilan 2D, dan saya memiliki sejumlah persegi panjang dalam suatu area di layar. Bagaimana cara menyebarkan kotak-kotak itu sehingga tidak saling tumpang tindih, tetapi hanya menyesuaikannya dengan gerakan minimal?

Posisi persegi panjang bersifat dinamis dan bergantung pada masukan pengguna, sehingga posisinya bisa di mana saja.

teks altGambar terlampir menunjukkan masalah dan solusi yang diinginkan

Masalah kehidupan nyata berkaitan dengan rollover, sebenarnya.

Jawaban atas pertanyaan di komentar

  1. Ukuran persegi panjang tidak tetap, dan tergantung pada panjang teks di rollover

  2. Soal ukuran layar, saat ini saya pikir lebih baik mengasumsikan bahwa ukuran layar cukup untuk persegi panjang. Jika ada terlalu banyak persegi panjang dan algo tidak menghasilkan solusi, maka saya hanya perlu mengubah isinya.

  3. Persyaratan untuk 'bergerak minimal' lebih untuk asetika daripada persyaratan teknik mutlak. Seseorang dapat memisahkan dua persegi panjang dengan menambahkan jarak yang sangat jauh di antara mereka, tetapi itu tidak akan terlihat bagus sebagai bagian dari GUI. Idenya adalah untuk mendapatkan rollover / persegi panjang sedekat mungkin dengan sumbernya (yang kemudian akan saya hubungkan ke sumber dengan garis hitam). Jadi baik 'memindahkan satu untuk x' atau 'memindahkan keduanya untuk setengah x' tidak masalah.

Extrakun
sumber
2
Dapatkah kita berasumsi bahwa persegi panjang selalu berorientasi horizontal atau vertikal, dan tidak miring pada porosnya pada suatu sudut?
Matt
2
Ya, anggapan itu valid.
Extrakun
Bisakah kita berasumsi bahwa layar selalu cukup besar untuk menopang persegi panjang tanpa tumpang tindih? Apakah persegi panjang selalu berukuran sama? Bisakah Anda lebih spesifik tentang apa artinya "bergerak minimal"? Misalnya, jika Anda memiliki 2 persegi panjang yang terletak persis di atas satu sama lain, apakah lebih baik hanya 1 di antaranya dengan jarak penuh untuk menghilangkan tumpang tindih, atau memindahkan keduanya setengah jarak?
Nick Larsen
@NickLarsen, saya telah menjawab pertanyaan Anda dalam jawaban yang diedit di atas. Terima kasih!
Extrakun
1
@ Joe: mungkin dia ingin memahami solusinya, sehingga dia dapat mendukungnya.
Beska

Jawaban:

97

Saya bekerja sedikit dalam hal ini, karena saya juga membutuhkan sesuatu yang serupa, tetapi saya telah menunda pengembangan algoritme. Anda membantu saya untuk mendapatkan beberapa dorongan: D

Saya juga membutuhkan kode sumbernya, jadi ini dia. Saya mengerjakannya di Mathematica, tetapi karena saya belum banyak menggunakan fitur fungsional, saya rasa akan mudah untuk menerjemahkan ke bahasa prosedural apa pun.

Perspektif bersejarah

Pertama saya memutuskan untuk mengembangkan algoritma untuk lingkaran, karena persimpangan lebih mudah dihitung. Itu hanya tergantung pada pusat dan jari-jari.

Saya bisa menggunakan pemecah persamaan Mathematica, dan kinerjanya bagus.

Hanya melihat:

teks alt

Itu mudah. Saya baru saja memuat pemecah dengan masalah berikut:

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
   ]

Sesederhana itu, dan Mathematica melakukan semua pekerjaannya.

Saya berkata "Ha! Itu mudah, sekarang mari kita pergi ke persegi panjang!". Tapi saya salah ...

Biru Persegi Panjang

Masalah utama dengan persegi panjang adalah menanyakan persimpangan adalah fungsi yang buruk. Sesuatu seperti:

Jadi, ketika saya mencoba memberi makan Mathematica dengan banyak kondisi untuk persamaan ini, kinerjanya sangat buruk sehingga saya memutuskan untuk melakukan sesuatu yang prosedural.

Algoritme saya berakhir sebagai berikut:

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

Anda mungkin memperhatikan bahwa kondisi "pergerakan terkecil" tidak sepenuhnya terpenuhi (hanya dalam satu arah). Tetapi saya menemukan bahwa memindahkan persegi panjang ke segala arah untuk memuaskannya, terkadang berakhir dengan perubahan peta yang membingungkan bagi pengguna.

Saat saya mendesain antarmuka pengguna, saya memilih untuk memindahkan persegi panjang sedikit lebih jauh, tetapi dengan cara yang lebih dapat diprediksi. Anda dapat mengubah algoritme untuk memeriksa semua sudut dan semua jari-jari yang mengelilingi posisinya saat ini hingga tempat kosong ditemukan, meskipun itu akan jauh lebih menuntut.

Bagaimanapun, ini adalah contoh hasil (sebelum / sesudah):

teks alt

Edit> Contoh lainnya di sini

Seperti yang Anda lihat, "pergerakan minimum" tidak memuaskan, tetapi hasilnya cukup baik.

Saya akan memposting kode di sini karena saya mengalami masalah dengan repositori SVN saya. Saya akan menghapusnya saat masalah teratasi.

Edit:

Anda juga dapat menggunakan R-Trees untuk menemukan persimpangan persegi panjang, tetapi tampaknya terlalu berlebihan untuk menangani sejumlah kecil persegi panjang. Dan saya belum menerapkan algoritme. Mungkin orang lain dapat mengarahkan Anda ke implementasi yang ada di platform pilihan Anda.

Peringatan! Kode adalah pendekatan pertama .. kualitasnya belum bagus, dan pasti memiliki beberapa bug.

Ini 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 *)];)
      ];

Utama

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!

Edit: Pencarian multi-sudut

Saya menerapkan perubahan dalam algoritme yang memungkinkan untuk mencari ke segala arah, tetapi memberikan preferensi pada sumbu yang dikenakan oleh simetri geometris.
Dengan mengorbankan lebih banyak siklus, ini menghasilkan konfigurasi akhir yang lebih ringkas, seperti yang Anda lihat di bawah ini:

masukkan deskripsi gambar di sini

Lebih banyak sampel di sini .

Pseudocode untuk main loop berubah menjadi:

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

Saya tidak menyertakan kode sumber agar singkatnya, tetapi tanyakan saja jika Anda merasa dapat menggunakannya. Saya pikir, jika Anda melakukannya dengan cara ini, lebih baik beralih ke pohon-R (banyak tes interval diperlukan di sini)

belisarius
sumber
4
Bagus. Teman saya dan saya sedang mencoba menerapkannya. silang jari Terima kasih atas waktu untuk memasang ini!
Extrakun
9
Menjelaskan proses berpikir, konsep algoritma, kesulitan & batasan, dan memberikan kode == +1. Dan lebih jika saya bisa menawarkannya.
Beska
1
@belisarlus Tulisan yang bagus! Apakah Anda pernah menjadikan sumber Anda publik?
Rohan West
Ada jawaban lain di sini yang mencoba menjawab ini dengan cara java. Adakah yang berhasil mem-porting solusi mathematica ini ke java?
mainstringargs
11

Ini tebakannya.

Temukan pusat C dari kotak pembatas persegi panjang Anda.

Untuk setiap persegi panjang R yang tumpang tindih dengan yang lain.

  1. Tentukan vektor gerakan v.
  2. Temukan semua persegi panjang R 'yang tumpang tindih dengan R.
  3. Tambahkan sebuah vektor ke v yang sebanding dengan vektor antara pusat R dan R '.
  4. Tambahkan sebuah vektor ke v yang sebanding dengan vektor antara C dan pusat R.
  5. Pindahkan R sebesar v.
  6. Ulangi sampai tidak ada yang tumpang tindih.

Ini secara bertahap menjauhkan persegi panjang dari satu sama lain dan dari tengah semua persegi panjang. Ini akan berhenti karena komponen v dari langkah 4 pada akhirnya akan menyebarkannya dengan sendirinya.

cape1232
sumber
Ide bagus menemukan pusat dan memindahkan persegi panjang di sekitarnya. +1 Satu-satunya masalah adalah menemukan pusat adalah masalah lain dengan sendirinya, dan masalah yang mungkin jauh lebih menantang untuk setiap persegi panjang yang Anda tambahkan.
Nick Larsen
2
Menemukan pusatnya mudah. Ambil saja nilai minimum dan maksimum dari sudut semua persegi panjang. Dan Anda hanya melakukannya sekali, tidak sekali per iterasi.
cape1232
Hal ini juga menghasilkan gerakan yang minimal, dalam arti bahwa persegi tidak akan bergerak jika tidak ada yang tumpang tindih. Oh, langkah 4 berhasil, jadi Anda harus melewati langkah 4 jika tidak ada tumpang tindih. Menemukan pengaturan sebenarnya yang membutuhkan gerakan minimal mungkin jauh lebih sulit.
cape1232
Untuk dua persegi panjang yang terletak di sudut area yang terlihat, alg harus dapat memahami apakah grafik harus diperluas atau dikontrak. Hanya mengomel. (Saya tahu bahwa visibilitas belum pada cakupan, tapi saya rasa penting untuk tidak menyelesaikan masalah hanya dengan memperluas grafik secukupnya, karena jika tidak solusinya adalah sepele: ambil dua kotak terdekat dan "iradiasikan" semua grafik dari pusat massanya cukup untuk memisahkan kedua persegi panjang tersebut). Pendekatan Anda lebih baik dari ini, tentu saja. Saya hanya mengatakan bahwa kita tidak boleh berkembang kecuali jika diperlukan.
Dr. belisarius
@belisarius Ini tidak berkembang jika tidak perlu. Setelah tidak ada yang tumpang tindih dengan persegi panjang Anda, persegi panjang berhenti bergerak. (Ini mungkin mulai lagi, tetapi hanya jika perlu.) Dengan persegi panjang yang cukup atau yang cukup besar, mungkin tidak dapat menampilkan semuanya di layar dalam ukuran penuh. Dalam hal ini, mudah untuk menemukan kotak pembatas dari solusi yang direspons dan menskalakan semuanya dengan jumlah yang sama sehingga pas di layar.
cape1232
6

Saya pikir solusi ini sangat mirip dengan yang diberikan oleh cape1232, tetapi sudah diterapkan, jadi patut dicoba :)

Ikuti diskusi reddit ini: http://www.reddit.com/r/gamedev/comments/1dlwc4/procedural_dungeon_generation_algorithm_explained/ dan lihat deskripsi dan implementasinya. Tidak ada kode sumber yang tersedia, jadi inilah pendekatan saya untuk masalah ini di AS3 (berfungsi persis sama, tetapi membuat persegi panjang tetap sesuai dengan resolusi kisi):

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;
            }
        }
    }
}
b005t3r
sumber
Ada kesalahan dalam logika. Sedangkan untuk sebuah ruangan, velocityadalah jumlah vektor antara pusatnya dan pusat ruangan lain, jika semua ruangan ditumpuk dengan bagian tengah yang sama, velocity.length == 0untuk semua ruangan dan tidak ada yang akan bergerak. Dengan cara yang sama, jika dua ruangan atau lebih memiliki persegi panjang yang sama dengan pusat yang sama, mereka akan bergerak bersama tetapi akan tetap bertumpuk.
Peyre
6

Saya sangat suka implementasi b005t3r! Ini berfungsi dalam kasus pengujian saya, namun perwakilan saya terlalu rendah untuk memberikan komentar dengan 2 perbaikan yang disarankan.

  1. Anda tidak boleh menerjemahkan ruangan dengan peningkatan resolusi tunggal, Anda harus menerjemahkan dengan kecepatan yang Anda hitung dengan susah payah! Hal ini membuat pemisahan lebih organik karena ruangan yang berpotongan dalam lebih banyak memisahkan setiap iterasi daripada ruangan yang tidak terlalu berpotongan dalam.

  2. Anda tidak boleh berasumsi velociites kurang dari 0,5 berarti ruangan terpisah karena Anda dapat terjebak dalam kasus di mana Anda tidak pernah terpisah. Bayangkan 2 ruangan berpotongan, tetapi tidak dapat mengoreksi dirinya sendiri karena setiap kali salah satu mencoba untuk mengoreksi penetrasi, mereka menghitung kecepatan yang diperlukan sebagai <0,5 sehingga mereka terus berulang.

Berikut adalah solusi 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);
Cord Rehn
sumber
4

Berikut adalah algoritma yang ditulis menggunakan Java untuk menangani sekelompok Rectangles yang tidak diputar . Ini memungkinkan Anda untuk menentukan rasio aspek yang diinginkan dari tata letak dan memposisikan kluster menggunakan parameterisasi Rectanglesebagai titik tautan , yang menjadi orientasi semua terjemahan yang dibuat. Anda juga dapat menentukan jumlah padding yang ingin Anda sebarkan Rectangle.

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

}

Berikut adalah contoh penggunaan AspectRatiodari 1.2, a FillPercentageof 0.8dan a Paddingof 10.0.

100 persegi panjang yang diskalakan dan didistribusikan secara acak.

100 persegi panjang acak didistribusikan menggunakan BoxxyDistribution.

Ini adalah pendekatan deterministik yang memungkinkan jarak terjadi di sekitar jangkar sambil membiarkan lokasi jangkar itu sendiri tidak berubah. Hal ini memungkinkan tata letak muncul di mana pun Tempat Menarik pengguna berada. Logika untuk memilih posisi cukup sederhana, tetapi menurut saya arsitektur sekitarnya yang mengurutkan elemen berdasarkan posisi awalnya dan kemudian mengulanginya adalah pendekatan yang berguna untuk menerapkan distribusi yang relatif dapat diprediksi. Selain itu, kami tidak mengandalkan tes persimpangan berulang atau semacamnya, hanya membangun beberapa kotak pembatas untuk memberi kami indikasi luas tentang di mana harus menyelaraskan sesuatu; Setelah ini, mengaplikasikan padding muncul secara alami.

Mapsy
sumber
3

Berikut adalah versi yang mengambil jawaban cape1232 dan merupakan contoh runnable mandiri untuk 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();

            }
        });
    }

}
mainstringargs
sumber