1 module gfm.math.box; 2 3 import std.math, 4 std.traits; 5 6 import gfm.math.vector, 7 gfm.math.funcs; 8 9 /// N-dimensional half-open interval [a, b[. 10 struct Box(T, int N) 11 { 12 static assert(N > 0); 13 14 public 15 { 16 alias Vector!(T, N) bound_t; 17 18 bound_t min; // not enforced, the box can have negative volume 19 bound_t max; 20 21 /// Construct a box which extends between 2 points. 22 /// Boundaries: min is inside the box, max is just outside. 23 @nogc this(bound_t min_, bound_t max_) pure nothrow 24 { 25 min = min_; 26 max = max_; 27 } 28 29 static if (N == 1) 30 { 31 @nogc this(T min_, T max_) pure nothrow 32 { 33 min.x = min_; 34 max.x = max_; 35 } 36 } 37 38 static if (N == 2) 39 { 40 @nogc this(T min_x, T min_y, T max_x, T max_y) pure nothrow 41 { 42 min = bound_t(min_x, min_y); 43 max = bound_t(max_x, max_y); 44 } 45 } 46 47 static if (N == 3) 48 { 49 @nogc this(T min_x, T min_y, T min_z, T max_x, T max_y, T max_z) pure nothrow 50 { 51 min = bound_t(min_x, min_y, min_z); 52 max = bound_t(max_x, max_y, max_z); 53 } 54 } 55 56 57 @property 58 { 59 /// Returns: Dimensions of the box. 60 @nogc bound_t size() pure const nothrow 61 { 62 return max - min; 63 } 64 65 /// Returns: Center of the box. 66 @nogc bound_t center() pure const nothrow 67 { 68 return (min + max) / 2; 69 } 70 71 /// Returns: Width of the box, always applicable. 72 static if (N >= 1) 73 @nogc T width() pure const nothrow @property 74 { 75 return max.x - min.x; 76 } 77 78 /// Returns: Height of the box, if applicable. 79 static if (N >= 2) 80 @nogc T height() pure const nothrow @property 81 { 82 return max.y - min.y; 83 } 84 85 /// Returns: Depth of the box, if applicable. 86 static if (N >= 3) 87 @nogc T depth() pure const nothrow @property 88 { 89 return max.z - min.z; 90 } 91 92 /// Returns: Signed volume of the box. 93 @nogc T volume() pure const nothrow 94 { 95 T res = 1; 96 bound_t size = size(); 97 for(int i = 0; i < N; ++i) 98 res *= size[i]; 99 return res; 100 } 101 102 /// Returns: true if empty. 103 @nogc bool empty() pure const nothrow 104 { 105 bound_t size = size(); 106 for(int i = 0; i < N; ++i) 107 if (size[i] == 0) 108 return true; 109 return false; 110 } 111 } 112 113 /// Returns: true if it contains point. 114 @nogc bool contains(bound_t point) pure const nothrow 115 { 116 assert(isSorted()); 117 for(int i = 0; i < N; ++i) 118 if ( !(point[i] >= min[i] && point[i] < max[i]) ) 119 return false; 120 121 return true; 122 } 123 124 /// Returns: true if it contains box other. 125 @nogc bool contains(Box other) pure const nothrow 126 { 127 assert(isSorted()); 128 assert(other.isSorted()); 129 130 for(int i = 0; i < N; ++i) 131 if ( (other.min[i] < min[i]) || (other.max[i] > max[i]) ) 132 return false; 133 return true; 134 } 135 136 /// Euclidean squared distance from a point. 137 /// See_also: Numerical Recipes Third Edition (2007) 138 @nogc double squaredDistance(bound_t point) pure const nothrow 139 { 140 assert(isSorted()); 141 double distanceSquared = 0; 142 for (int i = 0; i < N; ++i) 143 { 144 if (point[i] < min[i]) 145 distanceSquared += (point[i] - min[i]) ^^ 2; 146 147 if (point[i] > max[i]) 148 distanceSquared += (point[i] - max[i]) ^^ 2; 149 } 150 return distanceSquared; 151 } 152 153 /// Euclidean distance from a point. 154 /// See_also: squaredDistance. 155 @nogc double distance(bound_t point) pure const nothrow 156 { 157 return sqrt(squaredDistance(point)); 158 } 159 160 /// Euclidean squared distance from another box. 161 /// See_also: Numerical Recipes Third Edition (2007) 162 @nogc double squaredDistance(Box o) pure const nothrow 163 { 164 assert(isSorted()); 165 assert(o.isSorted()); 166 double distanceSquared = 0; 167 for (int i = 0; i < N; ++i) 168 { 169 if (o.max[i] < min[i]) 170 distanceSquared += (o.max[i] - min[i]) ^^ 2; 171 172 if (o.min[i] > max[i]) 173 distanceSquared += (o.min[i] - max[i]) ^^ 2; 174 } 175 return distanceSquared; 176 } 177 178 /// Euclidean distance from another box. 179 /// See_also: squaredDistance. 180 @nogc double distance(Box o) pure const nothrow 181 { 182 return sqrt(squaredDistance(o)); 183 } 184 185 /// Assumes sorted boxes. 186 /// This function deals with empty boxes correctly. 187 /// Returns: Intersection of two boxes. 188 @nogc Box intersection(Box o) pure const nothrow 189 { 190 assert(isSorted()); 191 assert(o.isSorted()); 192 193 // Return an empty box if one of the boxes is empty 194 if (empty()) 195 return this; 196 197 if (o.empty()) 198 return o; 199 200 Box result; 201 for (int i = 0; i < N; ++i) 202 { 203 T maxOfMins = (min.v[i] > o.min.v[i]) ? min.v[i] : o.min.v[i]; 204 T minOfMaxs = (max.v[i] < o.max.v[i]) ? max.v[i] : o.max.v[i]; 205 result.min.v[i] = maxOfMins; 206 result.max.v[i] = minOfMaxs >= maxOfMins ? minOfMaxs : maxOfMins; 207 } 208 return result; 209 } 210 211 /// Assumes sorted boxes. 212 /// This function deals with empty boxes correctly. 213 /// Returns: Intersection of two boxes. 214 @nogc bool intersects(Box other) pure const nothrow 215 { 216 Box inter = this.intersection(other); 217 return inter.isSorted() && !inter.empty(); 218 } 219 220 /// Extends the area of this Box. 221 @nogc Box grow(bound_t space) pure const nothrow 222 { 223 Box res = this; 224 res.min -= space; 225 res.max += space; 226 return res; 227 } 228 229 /// Shrink the area of this Box. The box might became unsorted. 230 @nogc Box shrink(bound_t space) pure const nothrow 231 { 232 return grow(-space); 233 } 234 235 /// Extends the area of this Box. 236 @nogc Box grow(T space) pure const nothrow 237 { 238 return grow(bound_t(space)); 239 } 240 241 /// Translate this Box. 242 @nogc Box translate(bound_t offset) pure const nothrow 243 { 244 return Box(min + offset, max + offset); 245 } 246 247 /// Shrinks the area of this Box. 248 /// Returns: Shrinked box. 249 @nogc Box shrink(T space) pure const nothrow 250 { 251 return shrink(bound_t(space)); 252 } 253 254 /// Expands the box to include point. 255 /// Returns: Expanded box. 256 @nogc Box expand(bound_t point) pure const nothrow 257 { 258 import vector = gfm.math.vector; 259 return Box(vector.minByElem(min, point), vector.maxByElem(max, point)); 260 } 261 262 /// Expands the box to include another box. 263 /// This function deals with empty boxes correctly. 264 /// Returns: Expanded box. 265 @nogc Box expand(Box other) pure const nothrow 266 { 267 assert(isSorted()); 268 assert(other.isSorted()); 269 270 // handle empty boxes 271 if (empty()) 272 return other; 273 if (other.empty()) 274 return this; 275 276 Box result; 277 for (int i = 0; i < N; ++i) 278 { 279 T minOfMins = (min.v[i] < other.min.v[i]) ? min.v[i] : other.min.v[i]; 280 T maxOfMaxs = (max.v[i] > other.max.v[i]) ? max.v[i] : other.max.v[i]; 281 result.min.v[i] = minOfMins; 282 result.max.v[i] = maxOfMaxs; 283 } 284 return result; 285 } 286 287 /// Returns: true if each dimension of the box is >= 0. 288 @nogc bool isSorted() pure const nothrow 289 { 290 for(int i = 0; i < N; ++i) 291 { 292 if (min[i] > max[i]) 293 return false; 294 } 295 return true; 296 } 297 298 /// Assign with another box. 299 @nogc ref Box opAssign(U)(U x) nothrow if (is(typeof(x.isBox))) 300 { 301 static if(is(U.element_t : T)) 302 { 303 static if(U._size == _size) 304 { 305 min = x.min; 306 max = x.max; 307 } 308 else 309 { 310 static assert(false, "no conversion between boxes with different dimensions"); 311 } 312 } 313 else 314 { 315 static assert(false, "no conversion from " ~ U.element_t.stringof ~ " to " ~ element_t.stringof); 316 } 317 return this; 318 } 319 320 /// Returns: true if comparing equal boxes. 321 @nogc bool opEquals(U)(U other) pure const nothrow if (is(U : Box)) 322 { 323 return (min == other.min) && (max == other.max); 324 } 325 } 326 327 private 328 { 329 enum isBox = true; 330 enum _size = N; 331 alias T element_t; 332 } 333 } 334 335 /// Instanciate to use a 2D box. 336 template box2(T) 337 { 338 alias Box!(T, 2) box2; 339 } 340 341 /// Instanciate to use a 3D box. 342 template box3(T) 343 { 344 alias Box!(T, 3) box3; 345 } 346 347 348 alias box2!int box2i; /// 2D box with integer coordinates. 349 alias box3!int box3i; /// 3D box with integer coordinates. 350 alias box2!float box2f; /// 2D box with float coordinates. 351 alias box3!float box3f; /// 3D box with float coordinates. 352 alias box2!double box2d; /// 2D box with double coordinates. 353 alias box3!double box3d; /// 3D box with double coordinates. 354 355 unittest 356 { 357 box2i a = box2i(1, 2, 3, 4); 358 assert(a.width == 2); 359 assert(a.height == 2); 360 assert(a.volume == 4); 361 box2i b = box2i(vec2i(1, 2), vec2i(3, 4)); 362 assert(a == b); 363 box2i c = box2i(0, 0, 1,1); 364 assert(c.translate(vec2i(3, 3)) == box2i(3, 3, 4, 4)); 365 assert(c.contains(vec2i(0, 0))); 366 assert(!c.contains(vec2i(1, 1))); 367 assert(b.contains(b)); 368 box2i d = c.expand(vec2i(3, 3)); 369 assert(d.contains(vec2i(2, 2))); 370 371 assert(d == d.expand(d)); 372 373 assert(!box2i(0, 0, 4, 4).contains(box2i(2, 2, 6, 6))); 374 375 assert(box2f(0, 0, 0, 0).empty()); 376 assert(!box2f(0, 2, 1, 1).empty()); 377 assert(!box2f(0, 0, 1, 1).empty()); 378 379 assert(box2i(260, 100, 360, 200).intersection(box2i(100, 100, 200, 200)).empty()); 380 381 // union with empty box is identity 382 assert(a.expand(box2i(10, 4, 10, 6)) == a); 383 384 // intersection with empty box is empty 385 assert(a.intersection(box2i(10, 4, 10, 6)).empty); 386 }