1 /**
2 * Copyright: Copyright Auburn Sounds 2015 and later.
3 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
4 * Authors:   Guillaume Piolat
5 */
6 module dplug.gui.boxlist;
7 
8 // Operations on list of boxes
9 
10 import std.algorithm.comparison;
11 
12 import dplug.core.alignedbuffer;
13 import gfm.math.box;
14 
15 /// Returns: Bounding boxes of all bounding boxes.
16 box2i boundingBox(box2i[] boxes) pure nothrow @nogc
17 {
18     if (boxes.length == 0)
19         return box2i(0, 0, 0, 0);
20     else
21     {
22         // Computes union of all boxes
23         box2i unionBox = boxes[0];
24         for(int i = 1; i < cast(int)boxes.length; ++i)
25             unionBox = unionBox.expand(boxes[i]);
26         return unionBox;
27     }
28 }
29 
30 
31 /// Make 4 boxes that are A without C (C is contained in A)
32 /// Some may be empty though since C touch at least one edge of A
33 
34 /// General case
35 /// +---------+               +---------+
36 /// |    A    |               |    D    |
37 /// |  +---+  |   After split +--+---+--+
38 /// |  | C |  |        =>     | E|   |F |   At least one of D, E, F or G is empty
39 /// |  +---+  |               +--+---+--+
40 /// |         |               |    G    |
41 /// +---------+               +---------+
42 void boxSubtraction(in box2i A, in box2i C, out box2i D, out box2i E, out box2i F, out box2i G) pure nothrow @nogc
43 {
44     D = box2i(A.min.x, A.min.y, A.max.x, C.min.y);
45     E = box2i(A.min.x, C.min.y, C.min.x, C.max.y);
46     F = box2i(C.max.x, C.min.y, A.max.x, C.max.y);
47     G = box2i(A.min.x, C.max.y, A.max.x, A.max.y);
48 }
49 
50 // Change the list of boxes so that the coverage is the same but none overlaps
51 // Every box pushed in filtered are non-intersecting.
52 // This may modify boxes in input.
53 // FUTURE: something better than O(n^2)
54 void removeOverlappingAreas(ref AlignedBuffer!box2i boxes, ref AlignedBuffer!box2i filtered) nothrow @nogc
55 {
56     for(int i = 0; i < cast(int)(boxes.length); ++i)
57     {
58         box2i A = boxes[i];
59 
60         assert(A.isSorted());
61 
62         // empty boxes aren't kept
63         if (A.volume() <= 0)
64             continue;
65 
66         bool foundIntersection = false;
67 
68         // test A against all other rectangles, if it pass, it is pushed
69         for(int j = i + 1; j < cast(int)(boxes.length); ++j)
70         {
71             box2i B = boxes[j];
72 
73             box2i C = A.intersection(B);
74             bool doesIntersect =  C.isSorted() && (!C.empty());
75 
76             if (doesIntersect)
77             {
78                 // case 1: A contains B, B is removed from the array, and no intersection considered
79                 if (A.contains(B))
80                 {
81                     // Remove that box since it has been dealt with
82                     boxes.removeAndReplaceByLastElement(j);
83                     j = j - 1;
84                     continue;
85                 }
86 
87                 foundIntersection = true; // A will not be pushed as is
88 
89                 if (B.contains(A))
90                 {
91                     break; // nothing from A is kept
92                 }
93                 else
94                 {
95                     // computes A without C
96                     box2i D, E, F, G;
97                     boxSubtraction(A, C, D, E, F, G);
98 
99                     if (!D.empty)
100                         boxes.pushBack(D);
101                     if (!E.empty)
102                         boxes.pushBack(E);
103                     if (!F.empty)
104                         boxes.pushBack(F);
105                     if (!G.empty)
106                         boxes.pushBack(G);
107 
108                     // no need to search for other intersection in A, since its parts have
109                     // been pushed
110                     break;
111                 }
112             }
113         }
114 
115         if (!foundIntersection)
116             filtered.pushBack(A);
117     }
118 }
119 
120 unittest
121 {
122     auto bl = makeAlignedBuffer!box2i();
123     bl.pushBack( box2i(0, 0, 4, 4) );
124     bl.pushBack( box2i(2, 2, 6, 6) );
125     bl.pushBack( box2i(1, 1, 2, 2) );
126 
127     import dplug.core.alignedbuffer;
128 
129     auto ab = makeAlignedBuffer!box2i();
130 
131     removeOverlappingAreas(bl, ab);
132     assert(ab[] == [ box2i(2, 2, 6, 6), box2i(0, 0, 4, 2), box2i(0, 2, 2, 4) ] );
133 
134     assert(bl[].boundingBox() == box2i(0, 0, 6, 6));
135 }
136 
137 
138 // Split each boxes in smaller boxes.
139 void tileAreas(in box2i[] areas, int maxWidth, int maxHeight, ref AlignedBuffer!box2i splitted) nothrow @nogc
140 {
141     foreach(area; areas)
142     {
143         assert(!area.empty);
144         int nWidth = (area.width + maxWidth - 1) / maxWidth;
145         int nHeight = (area.height + maxHeight - 1) / maxHeight;
146 
147         foreach (int j; 0..nHeight)
148         {
149             int y0 = maxHeight * j;
150             int y1 = std.algorithm.min(y0 + maxHeight, area.height);
151 
152             foreach (int i; 0..nWidth)
153             {
154                 int x0 = maxWidth * i;
155                 int x1 = std.algorithm.min(x0 + maxWidth, area.width);
156 
157                 box2i b = box2i(x0, y0, x1, y1).translate(area.min);
158                 assert(area.contains(b));
159                 splitted.pushBack(b);
160             }
161         }
162     }
163 }
164 
165 /// For debug purpose.
166 /// Returns: true if none of the boxes overlap.
167 bool haveNoOverlap(in box2i[] areas) nothrow @nogc
168 {
169     int N = cast(int)areas.length;
170 
171     // check every pair of boxes for overlap, inneficient
172     for (int i = 0; i < N; ++i)
173     {
174         assert(areas[i].isSorted());
175         for (int j = i + 1; j < N; ++j)
176         {
177             if (areas[i].intersects(areas[j]))
178                 return false;
179         }
180     }
181     return true;
182 }
183 
184 unittest
185 {
186     assert(haveNoOverlap([ box2i( 0, 0, 1, 1), box2i(1, 1, 2, 2) ]));
187     assert(!haveNoOverlap([ box2i( 0, 0, 1, 1), box2i(0, 0, 2, 1) ]));
188 }