อัลกอริทึมเพื่อเว้นวรรคสี่เหลี่ยมที่ทับซ้อนกัน?


93

ปัญหานี้เกี่ยวข้องกับการโรลโอเวอร์จริงๆฉันจะสรุปไว้ด้านล่างดังนี้:

ฉันมีมุมมอง 2 มิติและฉันมีรูปสี่เหลี่ยมจำนวนหนึ่งภายในพื้นที่บนหน้าจอ ฉันจะกางกล่องเหล่านั้นอย่างไรไม่ให้ซ้อนทับกัน แต่ปรับให้มีการเคลื่อนไหวน้อยที่สุดเท่านั้น

ตำแหน่งของรูปสี่เหลี่ยมผืนผ้าเป็นแบบไดนามิกและขึ้นอยู่กับข้อมูลที่ผู้ใช้ป้อนดังนั้นตำแหน่งของมันจึงอยู่ที่ใดก็ได้

ข้อความแสดงแทนภาพที่แนบมาแสดงปัญหาและแนวทางแก้ไขที่ต้องการ

ปัญหาในชีวิตจริงเกี่ยวข้องกับการโรลโอเวอร์

คำตอบสำหรับคำถามในความคิดเห็น

  1. ขนาดของสี่เหลี่ยมไม่คงที่และขึ้นอยู่กับความยาวของข้อความในโรลโอเวอร์

  2. เกี่ยวกับขนาดหน้าจอตอนนี้ฉันคิดว่ามันจะดีกว่าที่จะสมมติว่าขนาดของหน้าจอนั้นเพียงพอสำหรับสี่เหลี่ยม หากมีรูปสี่เหลี่ยมผืนผ้ามากเกินไปและ algo ไม่สามารถแก้ปัญหาได้ฉันก็ต้องปรับแต่งเนื้อหา

  3. ข้อกำหนดในการ 'เคลื่อนย้ายน้อยที่สุด' มีไว้สำหรับความไม่แน่นอนมากกว่าข้อกำหนดทางวิศวกรรมที่สมบูรณ์ เราสามารถเว้นวรรคสองรูปสี่เหลี่ยมได้โดยการเพิ่มระยะห่างระหว่างกันให้มาก แต่มันจะดูไม่ดีเมื่อเป็นส่วนหนึ่งของ GUI แนวคิดคือการทำให้โรลโอเวอร์ / สี่เหลี่ยมผืนผ้าใกล้เคียงกับแหล่งที่มา (ซึ่งฉันจะเชื่อมต่อกับแหล่งที่มาด้วยเส้นสีดำ) ดังนั้นไม่ว่าจะ 'ย้ายเพียงหนึ่งสำหรับ x' หรือ 'ย้ายทั้งสองสำหรับครึ่ง x' ก็ใช้ได้


2
เราสามารถสมมติว่ารูปสี่เหลี่ยมวางในแนวนอนหรือแนวตั้งเสมอและไม่เอียงบนแกนเป็นมุมหรือไม่?
แมตต์

2
ใช่สมมติฐานนั้นถูกต้อง
Extrakun

สมมติว่าหน้าจอใหญ่พอที่จะรองรับรูปสี่เหลี่ยมโดยไม่ทับซ้อนกันได้หรือไม่? รูปสี่เหลี่ยมมีขนาดเท่ากันหรือไม่? คุณสามารถเจาะจงมากขึ้นเกี่ยวกับความหมายของ "การเคลื่อนไหวน้อยที่สุด" ตัวอย่างเช่นหากคุณมีรูปสี่เหลี่ยม 2 รูปนั่งทับกันจะเป็นการดีกว่าที่จะมีเพียง 1 ในระยะเต็มเพื่อลบการทับซ้อนกันหรือย้ายทั้งสองไปครึ่งหนึ่งของระยะทาง?
Nick Larsen

@NickLarsen ฉันตอบคำถามของคุณในคำตอบที่แก้ไขด้านบนแล้ว ขอบคุณ!
พิเศษกุล

1
@joe: บางทีเขาอาจต้องการที่จะเข้าใจวิธีแก้ปัญหาเพื่อที่เขาจะได้สนับสนุน
Beska

คำตอบ:


97

ฉันกำลังทำงานเล็กน้อยในเรื่องนี้เนื่องจากฉันต้องการสิ่งที่คล้ายกัน แต่ฉันล่าช้าในการพัฒนาอัลกอริทึม คุณช่วยให้ฉันได้รับแรงกระตุ้น: 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-Treesในการค้นหาจุดตัดของรูปสี่เหลี่ยมผืนผ้า แต่ดูเหมือนว่าจะเกินความจำเป็นสำหรับการจัดการกับรูปสี่เหลี่ยมจำนวนน้อย และฉันยังไม่ได้ใช้อัลกอริทึม บางทีอาจมีคนอื่นชี้ให้คุณเห็นถึงการนำไปใช้งานบนแพลตฟอร์มที่คุณเลือก

คำเตือน! Code เป็นแนวทางแรก .. ยังมีคุณภาพไม่ดีนักและมีข้อบกพร่องอยู่บ้าง

มันคือ 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*);

เฮ้!

แก้ไข: การค้นหาหลายมุม

ฉันใช้การเปลี่ยนแปลงในอัลกอริทึมเพื่อให้สามารถค้นหาได้ทุกทิศทาง แต่ให้ความสำคัญกับแกนที่กำหนดโดยสมมาตรทางเรขาคณิต
ด้วยค่าใช้จ่ายของรอบที่มากขึ้นสิ่งนี้ส่งผลให้การกำหนดค่าขั้นสุดท้ายมีขนาดกะทัดรัดมากขึ้นดังที่คุณเห็นด้านล่างนี้:

ป้อนคำอธิบายภาพที่นี่

ตัวอย่างเพิ่มเติมที่นี่

pseudocode สำหรับลูปหลักเปลี่ยนเป็น:

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-trees (ต้องมีการทดสอบช่วงเวลามากมายที่นี่)


4
ทำได้ดีนี่. ฉันและเพื่อนกำลังพยายามนำไปใช้ ข้ามนิ้วขอบคุณที่สละเวลา!
Extrakun

9
อธิบายกระบวนการคิดแนวคิดอัลกอริทึมความยากลำบากและข้อ จำกัด และการให้รหัส == +1 และอีกมากมายถ้าฉันสามารถเสนอได้
Beska

1
@belisarlus เขียนได้ดีมาก! คุณเคยทำให้แหล่งที่มาของคุณเป็นสาธารณะหรือไม่?
Rohan West

มีคำตอบอื่น ๆ ที่นี่ซึ่งพยายามตอบด้วยวิธี java มีใครพอร์ทโซลูชัน mathematica นี้ไปยัง java ได้สำเร็จบ้าง?
mainstringargs

11

นี่คือการคาดเดา

หาจุดกลาง C ของกรอบสี่เหลี่ยมของคุณ

สำหรับแต่ละรูปสี่เหลี่ยมผืนผ้า R ที่ทับซ้อนกัน

  1. กำหนดเวกเตอร์การเคลื่อนไหว v.
  2. ค้นหารูปสี่เหลี่ยม R 'ทั้งหมดที่ซ้อนทับ R
  3. เพิ่มเวกเตอร์เป็น v ตามสัดส่วนกับเวกเตอร์ระหว่างจุดศูนย์กลางของ R และ R '
  4. เพิ่มเวกเตอร์เป็น v ตามสัดส่วนกับเวกเตอร์ระหว่าง C และศูนย์กลางของ R
  5. ย้าย R ตาม v.
  6. ทำซ้ำจนกว่าจะไม่มีอะไรทับซ้อนกัน

วิธีนี้จะเลื่อนรูปสี่เหลี่ยมออกจากกันและตรงกลางของสี่เหลี่ยมทั้งหมด สิ่งนี้จะยุติลงเนื่องจากส่วนประกอบของ v จากขั้นตอนที่ 4 จะกระจายออกไปทั้งหมดในที่สุด


ความคิดที่ดีในการหาจุดศูนย์กลางและย้ายรูปสี่เหลี่ยมเกี่ยวกับเรื่องนี้ +1 ปัญหาเดียวคือการหาจุดศูนย์กลางเป็นอีกปัญหาหนึ่งในตัวมันเองและปัญหาหนึ่งที่น่าจะท้าทายกว่าสำหรับสี่เหลี่ยมแต่ละอันที่คุณเพิ่มเข้าไป
Nick Larsen

2
การหาจุดศูนย์กลางเป็นเรื่องง่าย เพียงแค่ใช้ค่าต่ำสุดและสูงสุดของมุมของสี่เหลี่ยมทั้งหมด และคุณทำเพียงครั้งเดียวไม่ใช่ครั้งเดียวต่อการทำซ้ำ
Cape1232

ซึ่งส่งผลให้มีการเคลื่อนไหวน้อยที่สุดเช่นกันในแง่ที่ว่ามันจะไม่ย้ายสี่เหลี่ยมผืนผ้าหากไม่มีสิ่งใดทับซ้อนกัน โอ้ขั้นตอนที่ 4 ทำได้ดังนั้นคุณควรข้ามขั้นตอนที่ 4 หากไม่มีการทับซ้อนกัน การค้นหาการจัดเรียงจริงที่ต้องเคลื่อนไหวน้อยที่สุดนั้นยากกว่ามาก
Cape1232

สำหรับรูปสี่เหลี่ยมสองรูปที่อยู่ตรงมุมของพื้นที่ที่มองเห็นได้ alg ควรจะเข้าใจได้ว่าควรขยายหรือย่อกราฟ แค่คุยโว (ฉันรู้ว่าการมองเห็นไม่ได้อยู่ในขอบเขต แต่ฉันเดาว่ามันสำคัญที่จะไม่ต้องแก้ปัญหาด้วยการขยายกราฟให้เพียงพอเพราะถ้าไม่ใช่วิธีแก้ปัญหาก็ไม่สำคัญให้ใช้สี่เหลี่ยมสองช่องที่ใกล้ที่สุดแล้ว "ฉายรังสี" กราฟทั้งหมด จากจุดศูนย์กลางมวลมากพอที่จะแยกรูปสี่เหลี่ยมทั้งสองออกจากกัน) แนวทางของคุณดีกว่านี้แน่นอน ฉันแค่บอกว่าเราไม่ควรขยายถ้ามันจำเป็น
ดร. belisarius

@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สำหรับทุกห้องและจะไม่มีอะไรเคลื่อนไหวเลย ในทำนองเดียวกันถ้าห้องสองห้องขึ้นไปมีสี่เหลี่ยมผืนผ้าเดียวกันโดยมีจุดศูนย์กลางเดียวกันห้องเหล่านั้นจะเคลื่อนเข้าหากัน แต่จะยังคงซ้อนกัน
Peyre

6

ฉันชอบการใช้งานของ b005t3r มาก! ใช้งานได้ในกรณีทดสอบของฉัน แต่ตัวแทนของฉันต่ำเกินไปที่จะแสดงความคิดเห็นพร้อมกับการแก้ไขที่แนะนำ 2 รายการ

  1. คุณไม่ควรแปลห้องโดยเพิ่มความละเอียดเพียงครั้งเดียวคุณควรแปลตามความเร็วที่คุณเพิ่งคำนวณมาอย่างเจ็บปวด! สิ่งนี้ทำให้การแยกเป็นธรรมชาติมากขึ้นเนื่องจากห้องที่มีการตัดกันอย่างลึกจะแยกการวนซ้ำแต่ละครั้งได้มากกว่าห้องที่ตัดกันไม่ลึกมาก

  2. คุณไม่ควรคิดว่า velociites น้อยกว่า 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โดยการ

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

}

นี่คือตัวอย่างการใช้AspectRatioของ1.2มีFillPercentageของ0.8และของPadding10.0

100 รูปสี่เหลี่ยมที่ปรับขนาดและกระจายแบบสุ่ม

รูปสี่เหลี่ยมแบบสุ่ม 100 อันที่แจกจ่ายโดยใช้ BoxxyDistribution

นี่เป็นวิธีการกำหนดซึ่งช่วยให้ระยะห่างเกิดขึ้นรอบ ๆ จุดยึดในขณะที่ปล่อยให้ตำแหน่งของจุดยึดนั้นไม่เปลี่ยนแปลง สิ่งนี้ช่วยให้เค้าโครงเกิดขึ้นได้ทุกที่ที่ผู้ใช้เป็นจุดสนใจ ตรรกะในการเลือกตำแหน่งนั้นค่อนข้างง่าย แต่ฉันคิดว่าสถาปัตยกรรมโดยรอบของการจัดเรียงองค์ประกอบตามตำแหน่งเริ่มต้นจากนั้นจึงวนซ้ำเป็นแนวทางที่มีประโยชน์สำหรับการใช้การกระจายที่คาดเดาได้ค่อนข้างยาก นอกจากนี้เราไม่ได้อาศัยการทดสอบจุดตัดซ้ำ ๆ หรืออะไรทำนองนั้นเพียงแค่สร้างกรอบขอบขึ้นมาเพื่อให้เราระบุได้กว้าง ๆ ว่าจะจัดแนวสิ่งต่างๆ หลังจากนี้การใช้ padding ก็เกิดขึ้นตามธรรมชาติ


3

นี่คือเวอร์ชันที่ใช้คำตอบของ Cape1232 และเป็นตัวอย่างที่รันได้แบบสแตนด์อโลนสำหรับ Java:

public class Rectangles extends JPanel {

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

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

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

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

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

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

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

    List<Rectangle2D> rectanglesToDraw;

    protected void reset() {
        rectanglesToDraw = rectangles;

        this.repaint();
    }

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

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

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

        return intersections;
    }

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

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

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

        int movementFactor = 5;

        boolean hasIntersections = true;

        while (hasIntersections) {

            hasIntersections = false;

            for (Rectangle2D rect : rectanglesToDraw) {

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

                if (intersectingRects.size() > 0) {

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

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

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

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

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

                    }

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

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

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

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

            }
        }
        this.repaint();
    }

    private Rectangle2D surroundingRect(List<Rectangle2D> rectangles) {

        Point topLeft = null;
        Point bottomRight = null;

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

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

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

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

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

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

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

    }

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

        rects.reset();

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

        JPanel buttonsPanel = new JPanel();

        JButton fix = new JButton("Fix");

        fix.addActionListener(new ActionListener() {

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

            }
        });

        JButton resetButton = new JButton("Reset");

        resetButton.addActionListener(new ActionListener() {

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

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

        frame.add(buttonsPanel, BorderLayout.SOUTH);

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

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

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

            }
        });
    }

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.