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