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