Алгоритм простору прямокутників, що перекриваються?


92

Ця проблема насправді стосується перекидання, я просто узагальнюватиму нижче як такий:

У мене 2D-вигляд, і у мене є кілька прямокутників у межах області на екрані. Як розкласти ці коробки так, щоб вони не перекривали один одного, а лише регулювали їх з мінімальним рухом?

Позиції прямокутників динамічні та залежать від вводу користувача, тому їх позиції можуть бути де завгодно.

Вкладені текст заміщеннязображення показують проблему та бажане рішення

Насправді проблема реального життя стосується перекатів.

Відповіді на запитання в коментарях

  1. Розмір прямокутників не є фіксованим і залежить від довжини тексту у перекиданні

  2. Щодо розміру екрану, зараз я думаю, що краще припустити, що розміру екрану достатньо для прямокутників. Якщо занадто багато прямокутників, і алго не дає рішення, тоді мені просто потрібно налаштувати вміст.

  3. Вимога "рухатися мінімально" більше стосується асетики, ніж абсолютної технічної вимоги. Можна розставити два прямокутники, додавши велику відстань між ними, але це не буде виглядати добре як частина графічного інтерфейсу. Ідея полягає в тому, щоб зробити перекидання / прямокутник якомога ближче до його джерела (яке я потім підключу до джерела чорною лінією). Тож або «переміщення лише одного на х», або «переміщення обох на половину х» - це добре.


2
Чи можна припустити, що прямокутники завжди орієнтовані горизонтально або вертикально, а не нахилені на своїй осі під кутом?
Matt

2
Так, припущення справедливо.
Екстракун,

Чи можна припустити, що екран завжди достатньо великий, щоб підтримувати прямокутники без перекриття? Чи завжди прямокутники однакового розміру? Чи можете ви більш точно визначити, що означає "мінімальний рух"? Наприклад, якщо у вас є 2 прямокутники, які сидять точно один на одного, чи краще лише 1 з них на повну відстань, щоб видалити перекриття, або перемістити обидва на половину відстані?
Нік Ларсен,

@NickLarsen, я відповів на ваші запитання у відредагованій відповіді вище. Дякую!
Екстракун,

1
@joe: можливо, він хотів би зрозуміти рішення, щоб він міг його підтримати.
Беска

Відповіді:


95

Я трохи працював над цим, оскільки мені теж потрібно було щось подібне, але я затримав розробку алгоритму. Ви допомогли мені отримати певний імпульс: D

Мені також знадобився вихідний код, ось він. Я розробив це в Mathematica, але оскільки я не використовував в значній мірі функціональних можливостей, я думаю, це буде легко перекласти на будь-яку процедурну мову.

Історична перспектива

Спочатку я вирішив розробити алгоритм для кіл, тому що перетин легше обчислити. Це просто залежить від центрів та радіусів.

Я зміг використати вирішувач рівнянь Mathematica, і він добре працював.

Просто подивіться:

текст заміщення

Це було легко. Я щойно завантажив вирішувач із такою проблемою:

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

Настільки ж прямолінійно, і Mathematica зробила всю роботу.

Я сказав: "Ха! Це просто, тепер давайте підемо за прямокутниками!". Але я помилявся ...

Прямокутний блюз

Основна проблема прямокутників полягає в тому, що запит про перетин є неприємною функцією. Щось на зразок:

Отже, коли я спробував нагодувати Mathematica великою кількістю цих умов для рівняння, він працював настільки погано, що я вирішив зробити щось процедурне.

Мій алгоритм закінчився наступним чином:

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

Ви можете зауважити, що умова "найменшого руху" виконується не повністю (лише в одному напрямку). Але я виявив, що переміщення прямокутників у будь-якому напрямку для його задоволення іноді закінчується незрозумілою картою, що змінюється для користувача.

Оскільки я розробляю користувальницький інтерфейс, я вирішив перенести прямокутник трохи далі, але більш передбачуваним чином. Ви можете змінити алгоритм, щоб перевірити всі кути та всі радіуси, що оточують його поточне положення, поки не знайдеться порожнє місце, хоча це буде набагато вибагливіше.

У будь-якому випадку, це приклади результатів (до / після):

текст заміщення

Редагувати> Інші приклади тут

Як бачите, «мінімальний рух» не задоволений, але результати досить хороші.

Я розміщу код тут, оскільки у мене виникають проблеми з моїм сховищем SVN. Я видалю його, коли проблеми будуть вирішені.

Редагувати:

Ви також можете використовувати R-дерева для пошуку перехресть прямокутників, але це здається надмірним для роботи з невеликою кількістю прямокутників. І я ще не реалізував алгоритми. Можливо, хтось інший може вказати вам на існуючу реалізацію на вашій обраній платформі.

Увага! Код - це перший підхід .. ще не дуже якісний, і, безсумнівно, має деякі помилки.

Це Mathematica.

(*Define some functions first*)

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

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

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

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

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

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

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

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

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

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

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

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

Головна

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

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

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

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

workList = {}; (* the stack*)

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

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

While [workList != {},

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

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

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

    i = 1; (*movement quantity*)

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

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

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

HTH!

Редагувати: багатокутний пошук

Я реалізував зміну алгоритму, що дозволяє здійснювати пошук у всіх напрямках, але віддаючи перевагу осі, накладеної геометричною симетрією.
За рахунок більшої кількості циклів це призвело до більш компактних кінцевих конфігурацій, як ви можете бачити тут нижче:

введіть тут опис зображення

Більше зразків тут .

Псевдокод основного циклу змінено на:

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

Я не включаю вихідний код для стислості, але просто попросіть його, якщо вважаєте, що можете ним скористатися. Я думаю, що якщо ви підете цим шляхом, краще перейти на R-дерева (тут потрібно багато інтервальних тестів)


4
Хороший. Ми з моїм другом намагаємось це реалізувати. схрещені пальці Дякую за час, щоб це поставити!
Екстракун,

9
Пояснення процесу мислення, концепції алгоритму, труднощів та обмежень та надання коду == +1. І більше, якби я міг це запропонувати.
Беська

1
@belisarlus Чудово писати! Ви коли-небудь публікували своє джерело?
Rohan West

Тут є й інші відповіді, які намагаються відповісти на це java-способом. Хтось успішно переніс це рішення математики на Java?
mainstringargs

11

Ось здогадка.

Знайдіть центр С обмежувального вікна ваших прямокутників.

Для кожного прямокутника R, що перекриває інший.

  1. Визначте вектор руху v.
  2. Знайдіть усі прямокутники R ', що перекриваються R.
  3. Додайте вектор до v, пропорційний вектору між центром R і R '.
  4. Додайте вектор до v, пропорційний вектору між C та центром R.
  5. Перемістіть R на v.
  6. Повторюйте, поки нічого не збігається.

Це поступово зміщує прямокутники один від одного і центр усіх прямокутників. Це припиниться, тому що компонент v з кроку 4 з часом розподілить їх досить самостійно.


Хороша ідея знайти центр і перемістити прямокутники навколо нього. +1 Єдина проблема полягає в тому, що пошук центру - це ще одна проблема сама по собі, і така, яка, ймовірно, набагато складніша для кожного прямокутника, який ви додаєте.
Нік Ларсен,

2
Знайти центр легко. Просто візьміть мінімальний і максимальний кути всіх прямокутників. І ви робите це лише один раз, а не один раз за ітерацію.
cape1232

Це також призводить до мінімального переміщення, в тому сенсі, що він не рухає прямокутник, якщо нічого не перекриває його. О, крок 4 робить, тому вам слід пропустити крок 4, якщо немає перекриттів. Знайти справжнє розташування, яке вимагає мінімальних рухів, напевно, набагато складніше.
cape1232

Для двох прямокутників, розташованих у куті видимої області, алгоритм повинен мати можливість зрозуміти, чи слід графік розширювати чи стискати. Просто ганьбити. (Я знаю, що видимість ще не входить до сфери дії, але я думаю, важливо не вирішувати проблему, просто розширивши графік достатньо, бо якщо не рішення є тривіальним: візьміть два найближчі квадрати і "опромініть" весь графік від його центру маси достатньо, щоб ці два прямокутники були розділені). Звичайно, ваш підхід кращий за цей. Я просто кажу, що ми не повинні розширюватися, якщо це не потрібно.
Доктор Белісарій,

@belisarius Це не розширюється, якщо це не потрібно. Як тільки ніщо не перекриває ваш прямокутник, він перестає рухатися. (Може початися спочатку, але лише тоді, коли це буде потрібно.) Маючи достатньо прямокутників або досить великих, можливо, не вдасться показати їх усі на екрані в повному розмірі. У такому випадку легко знайти обмежувальну рамку розмальованого розчину і масштабувати все стільки ж, щоб вони помістилися на екрані.
cape1232

6

Я думаю, що це рішення дуже схоже на рішення cape1232, але воно вже реалізоване, тому варто перевірити :)

Слідкуйте за цим обговоренням на reddit: http://www.reddit.com/r/gamedev/comments/1dlwc4/procedural_dungeon_generation_algorithm_explained/ та ознайомтеся з описом та реалізацією. Немає доступного вихідного коду, тому ось мій підхід до цієї проблеми в AS3 (працює точно так само, але зберігає прямокутники, прив'язані до роздільної здатності сітки):

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

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

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

        _step = 1;

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

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

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

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

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

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

                    velocity = velocity.add(diff);
                }
            }

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

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

У логіці є недолік. Що стосується кімнати, velocityце сума векторів між її центром та центром інших кімнат, якщо всі кімнати складені одним і тим же центром, velocity.length == 0для всіх кімнат і ніколи ніколи не рухатиметься. Таким же чином, якщо дві або більше кімнат мають однаковий прямокутник з однаковим центром, вони рухатимуться разом, але залишатимуться складеними.
Пейр,

6

Мені дуже подобається реалізація b005t3r! Це працює в моїх тестових випадках, однак мій представник занадто низький, щоб залишити коментар із 2 запропонованими виправленнями.

  1. Ви не повинні перекладати кімнати з одинарним кроком, вам слід перекладати за швидкістю, яку ви просто болять, чітко розрахувавши! Це робить поділ більш органічним, оскільки глибоко перетинаються кімнати розділяють більше кожної ітерації, ніж не дуже глибоко перетинаються кімнати.

  2. Не слід вважати, що швидкість руху менше 0,5 означає, що кімнати окремі, оскільки ви можете застрягти у випадку, коли вас ніколи не розділять. Уявіть, що 2 кімнати перетинаються, але вони не в змозі виправити себе, тому що кожен раз, коли хтось намагається виправити проникнення, вони обчислюють необхідну швидкість як <0,5, тому вони нескінченно повторюються.

Ось рішення Java (: Вітаємо!

do {
    _separated = true;

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

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

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

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

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

                velocity.add(diff);
            }
        }

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

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

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

4

Ось алгоритм, написаний за допомогою Java для обробки кластера необертаних Rectangles. Це дозволяє вказати бажане співвідношення сторін макета та розмістити кластер, використовуючи параметризовану Rectangleяк опорну точку, на яку орієнтовані всі зроблені переклади. Ви також можете вказати довільну кількість заповнення, яке ви хочете розповсюдитиRectangle s.

public final class BoxxyDistribution {

/* Static Definitions. */
private static final int INDEX_BOUNDS_MINIMUM_X = 0;
private static final int INDEX_BOUNDS_MINIMUM_Y = 1;
private static final int INDEX_BOUNDS_MAXIMUM_X = 2;
private static final int INDEX_BOUNDS_MAXIMUM_Y = 3;

private static final double onCalculateMagnitude(final double pDeltaX, final double pDeltaY) {
    return Math.sqrt((pDeltaX * pDeltaX) + (pDeltaY + pDeltaY));
}

/* Updates the members of EnclosingBounds to ensure the dimensions of T can be completely encapsulated. */
private static final void onEncapsulateBounds(final double[] pEnclosingBounds, final double pMinimumX, final double pMinimumY, final double pMaximumX, final double pMaximumY) {
    pEnclosingBounds[0] = Math.min(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X], pMinimumX);
    pEnclosingBounds[1] = Math.min(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], pMinimumY);
    pEnclosingBounds[2] = Math.max(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X], pMaximumX);
    pEnclosingBounds[3] = Math.max(pEnclosingBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y], pMaximumY);
}

private static final void onEncapsulateBounds(final double[] pEnclosingBounds, final double[] pBounds) {
    BoxxyDistribution.onEncapsulateBounds(pEnclosingBounds, pBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X], pBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], pBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X], pBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y]);
}

private static final double onCalculateMidpoint(final double pMaximum, final double pMinimum) {
    return ((pMaximum - pMinimum) * 0.5) + pMinimum;
}

/* Re-arranges a List of Rectangles into something aesthetically pleasing. */
public static final void onBoxxyDistribution(final List<Rectangle> pRectangles, final Rectangle pAnchor, final double pPadding, final double pAspectRatio, final float pRowFillPercentage) {
    /* Create a safe clone of the Rectangles that we can modify as we please. */
    final List<Rectangle> lRectangles  = new ArrayList<Rectangle>(pRectangles);
    /* Allocate a List to track the bounds of each Row. */
    final List<double[]>  lRowBounds   = new ArrayList<double[]>(); // (MinX, MinY, MaxX, MaxY)
    /* Ensure Rectangles does not contain the Anchor. */
    lRectangles.remove(pAnchor);
    /* Order the Rectangles via their proximity to the Anchor. */
    Collections.sort(pRectangles, new Comparator<Rectangle>(){ @Override public final int compare(final Rectangle pT0, final Rectangle pT1) {
        /* Calculate the Distance for pT0. */
        final double lDistance0 = BoxxyDistribution.onCalculateMagnitude(pAnchor.getCenterX() - pT0.getCenterX(), pAnchor.getCenterY() - pT0.getCenterY());
        final double lDistance1 = BoxxyDistribution.onCalculateMagnitude(pAnchor.getCenterX() - pT1.getCenterX(), pAnchor.getCenterY() - pT1.getCenterY());
        /* Compare the magnitude in distance between the anchor and the Rectangles. */
        return Double.compare(lDistance0, lDistance1);
    } });
    /* Initialize the RowBounds using the Anchor. */ /** TODO: Probably better to call getBounds() here. **/
    lRowBounds.add(new double[]{ pAnchor.getX(), pAnchor.getY(), pAnchor.getX() + pAnchor.getWidth(), pAnchor.getY() + pAnchor.getHeight() });

    /* Allocate a variable for tracking the TotalBounds of all rows. */
    final double[] lTotalBounds = new double[]{ Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY };
    /* Now we iterate the Rectangles to place them optimally about the Anchor. */
    for(int i = 0; i < lRectangles.size(); i++) {
        /* Fetch the Rectangle. */
        final Rectangle lRectangle = lRectangles.get(i);
        /* Iterate through each Row. */
        for(final double[] lBounds : lRowBounds) {
            /* Update the TotalBounds. */
            BoxxyDistribution.onEncapsulateBounds(lTotalBounds, lBounds);
        }
        /* Allocate a variable to state whether the Rectangle has been allocated a suitable RowBounds. */
        boolean lIsBounded = false;
        /* Calculate the AspectRatio. */
        final double lAspectRatio = (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] - lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X]) / (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y] - lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y]);
        /* We will now iterate through each of the available Rows to determine if a Rectangle can be stored. */
        for(int j = 0; j < lRowBounds.size() && !lIsBounded; j++) {
            /* Fetch the Bounds. */
            final double[] lBounds = lRowBounds.get(j);
            /* Calculate the width and height of the Bounds. */
            final double   lWidth  = lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] - lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X];
            final double   lHeight = lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y] - lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y];
            /* Determine whether the Rectangle is suitable to fit in the RowBounds. */
            if(lRectangle.getHeight() <= lHeight && !(lAspectRatio > pAspectRatio && lWidth > pRowFillPercentage * (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] - lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X]))) {
                /* Register that the Rectangle IsBounded. */
                lIsBounded = true;
                /* Update the Rectangle's X and Y Co-ordinates. */
                lRectangle.setFrame((lRectangle.getX() > BoxxyDistribution.onCalculateMidpoint(lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X], lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X])) ? lBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_X] + pPadding : lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X] - (pPadding + lRectangle.getWidth()), lBounds[1], lRectangle.getWidth(), lRectangle.getHeight());
                /* Update the Bounds. (Do not modify the vertical metrics.) */
                BoxxyDistribution.onEncapsulateBounds(lTotalBounds, lRectangle.getX(), lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], lRectangle.getX() + lRectangle.getWidth(), lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y] + lHeight);
            }
        }
        /* Determine if the Rectangle has not been allocated a Row. */
        if(!lIsBounded) {
            /* Calculate the MidPoint of the TotalBounds. */
            final double lCentreY   = BoxxyDistribution.onCalculateMidpoint(lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y], lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y]);
            /* Determine whether to place the bounds above or below? */
            final double lYPosition = lRectangle.getY() < lCentreY ? lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y] - (pPadding + lRectangle.getHeight()) : (lTotalBounds[BoxxyDistribution.INDEX_BOUNDS_MAXIMUM_Y] + pPadding);
            /* Create a new RowBounds. */
            final double[] lBounds  = new double[]{ pAnchor.getX(), lYPosition, pAnchor.getX() + lRectangle.getWidth(), lYPosition + lRectangle.getHeight() };
            /* Allocate a new row, roughly positioned about the anchor. */
            lRowBounds.add(lBounds);
            /* Position the Rectangle. */
            lRectangle.setFrame(lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_X], lBounds[BoxxyDistribution.INDEX_BOUNDS_MINIMUM_Y], lRectangle.getWidth(), lRectangle.getHeight());
        }
    }
}

}

Ось приклад використання AspectRatioof of 1.2, a FillPercentageof 0.8і a Paddingof 10.0.

100 випадково масштабованих та розподілених прямокутників.

100 випадкових прямокутників, розподілених за допомогою BoxxyDistribution.

Це детермінований підхід, який дозволяє відстань виникати навколо якоря, залишаючи при цьому розташування самого якоря незмінним. Це дозволяє розміщення розміщуватись у будь-якій точці інтересу користувача. Логіка вибору позиції досить спрощена, але я думаю, що оточуюча архітектура сортування елементів на основі їх початкового положення та подальшої ітерації є корисним підходом для реалізації відносно передбачуваного розподілу. До того ж ми не покладаємось на ітеративні тести перетину чи щось подібне, просто створюємо деякі обмежувальні рамки, щоб дати нам широку вказівку, де слід вирівняти речі; після цього нанесення прокладок стає природним.


3

Ось версія, яка приймає відповідь cape1232 і є самостійним прикладом для запуску для Java:

public class Rectangles extends JPanel {

    List<Rectangle2D> rectangles = new ArrayList<Rectangle2D>();
    {
        // x,y,w,h
        rectangles.add(new Rectangle2D.Float(300, 50, 50, 50));

        rectangles.add(new Rectangle2D.Float(300, 50, 20, 50));

        rectangles.add(new Rectangle2D.Float(100, 100, 100, 50));

        rectangles.add(new Rectangle2D.Float(120, 200, 50, 50));

        rectangles.add(new Rectangle2D.Float(150, 130, 100, 100));

        rectangles.add(new Rectangle2D.Float(0, 100, 100, 50));

        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                rectangles.add(new Rectangle2D.Float(i * 40, j * 40, 20, 20));
            }
        }
    }

    List<Rectangle2D> rectanglesToDraw;

    protected void reset() {
        rectanglesToDraw = rectangles;

        this.repaint();
    }

    private List<Rectangle2D> findIntersections(Rectangle2D rect, List<Rectangle2D> rectList) {

        ArrayList<Rectangle2D> intersections = new ArrayList<Rectangle2D>();

        for (Rectangle2D intersectingRect : rectList) {
            if (!rect.equals(intersectingRect) && intersectingRect.intersects(rect)) {
                intersections.add(intersectingRect);
            }
        }

        return intersections;
    }

    protected void fix() {
        rectanglesToDraw = new ArrayList<Rectangle2D>();

        for (Rectangle2D rect : rectangles) {
            Rectangle2D copyRect = new Rectangle2D.Double();
            copyRect.setRect(rect);
            rectanglesToDraw.add(copyRect);
        }

        // Find the center C of the bounding box of your rectangles.
        Rectangle2D surroundRect = surroundingRect(rectanglesToDraw);
        Point center = new Point((int) surroundRect.getCenterX(), (int) surroundRect.getCenterY());

        int movementFactor = 5;

        boolean hasIntersections = true;

        while (hasIntersections) {

            hasIntersections = false;

            for (Rectangle2D rect : rectanglesToDraw) {

                // Find all the rectangles R' that overlap R.
                List<Rectangle2D> intersectingRects = findIntersections(rect, rectanglesToDraw);

                if (intersectingRects.size() > 0) {

                    // Define a movement vector v.
                    Point movementVector = new Point(0, 0);

                    Point centerR = new Point((int) rect.getCenterX(), (int) rect.getCenterY());

                    // For each rectangle R that overlaps another.
                    for (Rectangle2D rPrime : intersectingRects) {
                        Point centerRPrime = new Point((int) rPrime.getCenterX(), (int) rPrime.getCenterY());

                        int xTrans = (int) (centerR.getX() - centerRPrime.getX());
                        int yTrans = (int) (centerR.getY() - centerRPrime.getY());

                        // Add a vector to v proportional to the vector between the center of R and R'.
                        movementVector.translate(xTrans < 0 ? -movementFactor : movementFactor,
                                yTrans < 0 ? -movementFactor : movementFactor);

                    }

                    int xTrans = (int) (centerR.getX() - center.getX());
                    int yTrans = (int) (centerR.getY() - center.getY());

                    // Add a vector to v proportional to the vector between C and the center of R.
                    movementVector.translate(xTrans < 0 ? -movementFactor : movementFactor,
                            yTrans < 0 ? -movementFactor : movementFactor);

                    // Move R by v.
                    rect.setRect(rect.getX() + movementVector.getX(), rect.getY() + movementVector.getY(),
                            rect.getWidth(), rect.getHeight());

                    // Repeat until nothing overlaps.
                    hasIntersections = true;
                }

            }
        }
        this.repaint();
    }

    private Rectangle2D surroundingRect(List<Rectangle2D> rectangles) {

        Point topLeft = null;
        Point bottomRight = null;

        for (Rectangle2D rect : rectangles) {
            if (topLeft == null) {
                topLeft = new Point((int) rect.getMinX(), (int) rect.getMinY());
            } else {
                if (rect.getMinX() < topLeft.getX()) {
                    topLeft.setLocation((int) rect.getMinX(), topLeft.getY());
                }

                if (rect.getMinY() < topLeft.getY()) {
                    topLeft.setLocation(topLeft.getX(), (int) rect.getMinY());
                }
            }

            if (bottomRight == null) {
                bottomRight = new Point((int) rect.getMaxX(), (int) rect.getMaxY());
            } else {
                if (rect.getMaxX() > bottomRight.getX()) {
                    bottomRight.setLocation((int) rect.getMaxX(), bottomRight.getY());
                }

                if (rect.getMaxY() > bottomRight.getY()) {
                    bottomRight.setLocation(bottomRight.getX(), (int) rect.getMaxY());
                }
            }
        }

        return new Rectangle2D.Double(topLeft.getX(), topLeft.getY(), bottomRight.getX() - topLeft.getX(),
                bottomRight.getY() - topLeft.getY());
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;

        for (Rectangle2D entry : rectanglesToDraw) {
            g2d.setStroke(new BasicStroke(1));
            // g2d.fillRect((int) entry.getX(), (int) entry.getY(), (int) entry.getWidth(),
            // (int) entry.getHeight());
            g2d.draw(entry);
        }

    }

    protected static void createAndShowGUI() {
        Rectangles rects = new Rectangles();

        rects.reset();

        JFrame frame = new JFrame("Rectangles");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());
        frame.add(rects, BorderLayout.CENTER);

        JPanel buttonsPanel = new JPanel();

        JButton fix = new JButton("Fix");

        fix.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                rects.fix();

            }
        });

        JButton resetButton = new JButton("Reset");

        resetButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                rects.reset();
            }
        });

        buttonsPanel.add(fix);
        buttonsPanel.add(resetButton);

        frame.add(buttonsPanel, BorderLayout.SOUTH);

        frame.setSize(400, 400);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                createAndShowGUI();

            }
        });
    }

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.