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 }