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 }