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