Örtüşen dikdörtgenleri aralamak için bir algoritma mı?


94

Bu problem aslında devreden çıkarmalarla ilgilidir, aşağıda şu şekilde genelleştireceğim:

2B görünümüm var ve ekrandaki bir alan içinde birkaç dikdörtgen var. Bu kutuları birbiriyle örtüşmeyecek şekilde nasıl yayabilirim, ancak minimum hareketle ayarlayabilirim?

Dikdörtgenlerin konumları dinamiktir ve kullanıcının girdisine bağlıdır, bu nedenle konumları herhangi bir yerde olabilir.

Ekteki alternatif metinresimler sorunu ve istenen çözümü gösterir

Gerçek hayat problemi, aslında devirlerle ilgilidir.

Yorumlardaki soruların cevapları

  1. Dikdörtgenlerin boyutu sabit değildir ve rollover'daki metnin uzunluğuna bağlıdır

  2. Ekran boyutu hakkında, şu anda ekranın boyutunun dikdörtgenler için yeterli olduğunu varsaymanın daha iyi olacağını düşünüyorum. Çok fazla dikdörtgen varsa ve algo çözüm üretmiyorsa, o zaman içeriği değiştirmem gerekiyor.

  3. 'Minimal hareket etme' gerekliliği, estetik için mutlak bir mühendislik gerekliliğinden daha fazlasıdır. Biri, aralarına geniş bir mesafe ekleyerek iki dikdörtgeni ayırabilir, ancak bu, GUI'nin bir parçası olarak iyi görünmeyecektir. Buradaki fikir, rollover / dikdörtgeni kaynağına en yakın hale getirmektir (daha sonra kaynağa siyah bir çizgi ile bağlanacağım). Yani ya 'x için sadece bir tane hareket ettirmek' ya da 'yarım x için ikisini birden hareket ettirmek' iyidir.


2
Dikdörtgenlerin her zaman yatay veya dikey olarak yönlendirildiğini ve eksenlerinde belirli bir açıyla eğilmediğini varsayabilir miyiz?
Matt

2
Evet, varsayım geçerlidir.
Ekstrakun

Ekranın her zaman dikdörtgenleri örtüşmeden destekleyecek kadar büyük olduğunu varsayabilir miyiz? Dikdörtgenler her zaman aynı boyutta mıdır? "Minimum hareket" in ne anlama geldiği konusunda daha net olabilir misiniz? Örneğin, tam olarak üst üste oturan 2 dikdörtgene sahipseniz, üst üste binmeyi ortadan kaldırmak veya her ikisini de mesafenin yarısını taşımak için tam mesafe kadar sadece 1 tanesini kullanmak daha mı iyidir?
Nick Larsen

@NickLarsen, sorularınızı yukarıdaki düzenlenmiş cevapta cevapladım. Teşekkürler!
Ekstrakun

1
@joe: Belki de çözümü anlamak ister, böylece onu destekleyebilir.
Beska

Yanıtlar:


97

Ben de benzer bir şeye ihtiyacım olduğu için bunun üzerinde biraz çalışıyordum ama algoritma geliştirmeyi geciktirmiştim. Biraz dürtü almama yardım ettin: D

Kaynak koduna da ihtiyacım vardı, işte burada. Bunu Mathematica'da çözdüm, ancak işlevsel özellikleri yoğun bir şekilde kullanmadığım için, herhangi bir prosedürel dile tercüme etmek kolay olacağını tahmin ediyorum.

Tarihi bir bakış açısı

İlk önce daireler için algoritma geliştirmeye karar verdim, çünkü kesişimin hesaplanması daha kolay. Sadece merkezlere ve yarıçaplara bağlıdır.

Mathematica denklem çözücüsünü kullanabildim ve iyi bir performans gösterdi.

Sadece bakmak:

alternatif metin

Kolaydı. Çözücüyü az önce aşağıdaki problemle yükledim:

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
   ]

Bu kadar basit ve Mathematica tüm işi yaptı.

"Ha! Çok kolay, şimdi dikdörtgenler için gidelim!" Dedim. Ama yanılmışım ...

Dikdörtgen Blues

Dikdörtgenlerle ilgili temel sorun, kesişimin sorgulanmasının kötü bir işlev olmasıdır. Gibi bir şey:

Bu yüzden, Mathematica'yı denklem için bu koşulların çoğuyla beslemeye çalıştığımda, o kadar kötü bir performans gösterdi ki, prosedürle ilgili bir şey yapmaya karar verdim.

Algoritmam şu şekilde sonuçlandı:

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

"En küçük hareket" koşulunun tamamen tatmin edilmediğini fark edebilirsiniz (sadece bir yönde). Ancak, dikdörtgenleri tatmin etmek için herhangi bir yönde hareket ettirmenin, bazen kullanıcı için kafa karıştırıcı bir haritanın değişmesine neden olduğunu buldum.

Bir kullanıcı arayüzü tasarlarken, dikdörtgeni biraz daha ileriye taşımayı seçiyorum, ancak daha öngörülebilir bir şekilde. Algoritmayı, boş bir yer bulunana kadar mevcut konumunu çevreleyen tüm açıları ve tüm yarıçapları incelemek için değiştirebilirsiniz, ancak bu çok daha zorlu olacaktır.

Her neyse, bunlar sonuçların örnekleridir (önce / sonra):

alternatif metin

Düzenle> Burada daha fazla örnek

Görebileceğiniz gibi, "minimum hareket" tatmin edici değil, ancak sonuçlar yeterince iyi.

Kodu buraya göndereceğim çünkü SVN depomla ilgili bazı sorunlar yaşıyorum. Sorunlar çözüldüğünde onu kaldıracağım.

Düzenle:

Dikdörtgen kesişimlerini bulmak için R-Ağaçları da kullanabilirsiniz , ancak az sayıda dikdörtgenle uğraşmak aşırı bir şey gibi görünüyor. Ve algoritmaları henüz uygulamadım. Belki başka biri sizi seçtiğiniz platformda mevcut bir uygulamaya yönlendirebilir.

Uyarı! Kod ilk yaklaşımdır .. henüz çok kaliteli değildir ve kesinlikle bazı hatalar içerir.

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

Ana

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!

Düzenleme: Çok açılı arama

Algoritmada her yönden arama yapmaya izin veren bir değişiklik uyguladım, ancak geometrik simetrinin dayattığı ekseni tercih ettim.
Daha fazla döngü pahasına, bu, aşağıda görebileceğiniz gibi, daha kompakt son yapılandırmalarla sonuçlandı:

görüntü açıklamasını buraya girin

Daha fazla örnek burada .

Ana döngü için sözde kod şu şekilde değiştirildi:

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

Kısaca kaynak kodunu dahil etmiyorum, ancak kullanabileceğinizi düşünüyorsanız isteyin. Bence bu şekilde giderseniz, R-ağaçlarına geçmeniz daha iyi olur (burada çok sayıda aralık testi gereklidir)


4
Güzel bir. Arkadaşım ve ben onu uygulamaya çalışıyoruz. çapraz parmaklar Bunu koymak için zaman ayırdığınız için teşekkürler!
Ekstrakun

9
Düşünce sürecini, algoritma kavramını, zorlukları ve sınırlamaları açıklamak ve kod == +1 sağlamak. Ve daha fazlasını sunabilirsem.
Beska

1
@belisarlus Harika bir yazı! Kaynağınızı hiç halka açıkladınız mı?
Rohan West

Burada bunu java yoluyla cevaplamaya çalışan başka cevaplar var. Bu mathematica çözümünü başarıyla java'ya taşıyan var mı?
mainstringargs

11

İşte bir tahmin.

Dikdörtgenlerinizin sınırlayıcı kutusunun C merkezini bulun.

Bir diğeriyle örtüşen her dikdörtgen R için.

  1. Bir hareket vektörü tanımlama v.
  2. R ile örtüşen tüm R 'dikdörtgenlerini bulun.
  3. V 'ye R ve R' merkezi arasındaki vektöre orantılı bir vektör ekleyin.
  4. V'ye C ile R'nin merkezi arasındaki vektöre orantılı bir vektör ekleyin.
  5. R'yi v.
  6. Hiçbir şey çakışmayana kadar tekrarlayın.

Bu, dikdörtgenleri aşamalı olarak birbirinden ve tüm dikdörtgenlerin ortasından uzaklaştırır. Bu sona erecek çünkü 4. adımdaki v bileşeni sonunda onları kendi başına yeterince yayacaktır.


Merkezi bulmak ve etrafındaki dikdörtgenleri hareket ettirmek iyi fikir. +1 Tek sorun, merkezi bulmanın başlı başına başka bir sorun olması ve eklediğiniz her dikdörtgen için muhtemelen çok daha zorlayıcı olmasıdır.
Nick Larsen

2
Merkezi bulmak kolaydır. Sadece tüm dikdörtgenlerin köşelerinin minimum ve maksimum değerlerini alın. Ve bunu her yinelemede bir kez değil, yalnızca bir kez yaparsınız.
cape1232

Bu aynı zamanda minimum hareketle sonuçlanır, yani hiçbir şey örtüşmüyorsa bir dikdörtgeni hareket ettirmez. Ah, 4. adım öyle, bu yüzden herhangi bir çakışma yoksa 4. adımı atlamalısın. Minimum hareket gerektiren gerçek düzeni bulmak muhtemelen çok daha zordur.
cape1232

Görünür alanın bir köşesinde bulunan iki dikdörtgen için alg, grafiğin genişletilmesi veya daraltılması gerektiğini anlayabilmelidir. Sadece ranting. (Görünürlüğün henüz kapsamda olmadığını biliyorum, ancak sorunu sadece grafiği yeterince genişleterek çözmemek önemli, çünkü çözüm önemsiz değilse: en yakın iki kareyi alın ve tüm grafiği "ışınlayın" bu iki dikdörtgeni birbirinden ayıracak kadar kütle merkezinden). Elbette yaklaşımınız bundan daha iyi. Sadece gerekli olmadıkça genişlemememiz gerektiğini söylüyorum.
Dr Belisarius

@belisarius Gerekli değilse bu genişlemez. Dikdörtgeninizle hiçbir şey çakışmadığında hareket etmeyi durdurur. (Yeniden başlayabilir, ancak yalnızca gerektiğinde.) Yeterli dikdörtgenler veya yeterince büyük olanlarla, bunların tümünü ekranda tam boyutta göstermek mümkün olmayabilir. Bu durumda, tekrarlanan çözümün sınırlayıcı kutusunu bulmak ve ekrana sığacak şekilde her şeyi aynı miktarda ölçeklendirmek kolaydır.
cape1232

6

Bu çözümün cape1232 tarafından verilene oldukça benzediğini düşünüyorum, ancak zaten uygulandı, bu yüzden kontrol etmeye değer :)

Bu reddit tartışmasını takip edin: http://www.reddit.com/r/gamedev/comments/1dlwc4/procedural_dungeon_generation_algorithm_explained/ ve açıklamaya ve uygulamaya göz atın. Kullanılabilir kaynak kodu yok, bu yüzden AS3'teki bu soruna yaklaşımım şu şekildedir (tamamen aynı şekilde çalışır, ancak dikdörtgenleri ızgaranın çözünürlüğüne bağlı tutar):

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

Mantıkta bir kusur var. Bir odaya gelince velocity, merkezi ile diğer odaların merkezi arasındaki vektörlerin toplamıdır, eğer tüm odalar aynı merkezle istiflenmişse, velocity.length == 0tüm odalar için ve hiçbir şey hareket etmeyecektir. Aynı şekilde, iki veya daha fazla oda aynı merkezle aynı dikdörtgene sahipse, birlikte hareket edecekler ancak üst üste kalacaktır.
Peyre

6

B005t3r'nin uygulanmasını gerçekten çok seviyorum! Test durumlarımda çalışıyor, ancak temsilcim önerilen 2 düzeltmeyle bir yorum bırakamayacak kadar düşük.

  1. Odaları tek bir çözünürlük artışıyla çevirmemelisiniz, acıyla hesapladığınız hız ile çevirmelisiniz! Derinlemesine kesişen odalar her yinelemeyi derinlemesine kesişmeyen odalardan daha fazla ayırdığı için bu, ayrımı daha organik hale getirir.

  2. Hiçbir zaman ayrılmadığınız bir durumda sıkışıp kalabileceğiniz için, 0,5'ten daha düşük hızlarda odaların ayrı olduğunu varsaymamalısınız. İki odanın kesiştiğini, ancak kendilerini düzeltemediklerini hayal edin, çünkü biri penetrasyonu düzeltmeye çalıştığında, gerekli hızı <0.5 olarak hesaplarlar, böylece sonsuz bir şekilde yinelenirler.

İşte bir Java çözümü (: Şerefe!

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

Döndürülmemiş URL'lerin bir kümesini işlemek için Java kullanılarak yazılmış bir algoritma Rectangle. Düzenin istenen en boy oranını belirlemenize ve kümeyi Rectangle, yapılan tüm çevirilerin yönlendirildiği bir çapa noktası olarak parametrelendirilmiş bir kullanarak konumlandırmanıza olanak tanır . Ayrıca, e-postaları yaymak istediğiniz rastgele bir dolgu miktarı da belirtebilirsiniz 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());
        }
    }
}

}

İşte bir kullanıldığı bir örnek AspectRatioarasında 1.2, bir FillPercentageait 0.8ve Paddingiçinde 10.0.

100 rastgele ölçeklenmiş ve dağıtılmış dikdörtgen.

BoxxyDistribution kullanılarak dağıtılan 100 rastgele dikdörtgen.

Bu, ankrajın yerini değiştirmeden bırakırken çapanın etrafında boşluk oluşmasına izin veren deterministik bir yaklaşımdır. Bu, mizanpajın kullanıcının İlgi Çekici Noktasının olduğu her yerde gerçekleşmesini sağlar. Bir konum seçmenin mantığı oldukça basittir, ancak öğeleri ilk konumlarına göre sıralayan ve ardından yineleyen çevreleyen mimarinin, nispeten öngörülebilir bir dağıtım uygulamak için yararlı bir yaklaşım olduğunu düşünüyorum. Artı, yinelemeli kesişim testlerine veya bunun gibi bir şeye güvenmiyoruz, sadece bize şeyleri nereye hizalayacağımız konusunda geniş bir gösterge vermek için sınırlayıcı kutular oluşturuyoruz; Bundan sonra, dolgu uygulamak doğal olarak gelir.


3

İşte cape1232'nin cevabını alan ve Java için bağımsız çalıştırılabilir bir örnek olan bir sürüm:

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

            }
        });
    }

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.