1 // Copyright Ferdinand Majerech 2014. 2 // Distributed under the Boost Software License, Version 1.0. 3 // (See accompanying file LICENSE_1_0.txt or copy at 4 // http://www.boost.org/LICENSE_1_0.txt) 5 6 7 /// Axis-aligned bounding boxes. Based on gfm.math.box box, not gl3n. 8 module gl3n_extra.box; 9 10 import gl3n_extra.linalg; 11 12 /// N-dimensional half-open interval [a, b[. 13 struct Box(T, size_t N) 14 { 15 @safe @nogc: 16 static assert(N > 0); 17 18 public: 19 alias Vector!(T, N) Bound; 20 21 Bound min; // not enforced, the box can have negative volume 22 Bound max; 23 24 /// Construct a box which extends between 2 points. 25 /// Boundaries: min is inside the box, max is just outside. 26 this(Bound min_, Bound max_) pure nothrow 27 { 28 foreach(i; 0 .. N) 29 { 30 min.vector[i] = .min(min_.vector[i], max_.vector[i]); 31 max.vector[i] = .max(min_.vector[i], max_.vector[i]); 32 } 33 } 34 35 static if (N == 1u) 36 this(T min_, T max_) pure nothrow 37 { 38 this(Bound(min_), Bound(max_)); 39 } 40 41 static if (N == 2u) 42 this(T min_x, T min_y, T max_x, T max_y) pure nothrow 43 { 44 this(Bound(min_x, min_y), Bound(max_x, max_y)); 45 } 46 47 static if (N == 3u) 48 this(T min_x, T min_y, T min_z, T max_x, T max_y, T max_z) pure nothrow 49 { 50 this(Bound(min_x, min_y, min_z), Bound(max_x, max_y, max_z)); 51 } 52 53 54 /// Returns: Dimensions of the box. 55 Bound size() pure const nothrow 56 { 57 return max - min; 58 } 59 60 /// Returns: Area of the box. 61 T area() pure const nothrow 62 { 63 return cast(T)size.magnitude_squared; 64 } 65 66 /// Returns: Center of the box. 67 Bound center() pure const nothrow 68 { 69 Bound.vt[Bound.dimension] vector = (min.vector[] + max.vector[]) / 2; 70 return Bound(vector); 71 } 72 73 /// Returns: Width of the box, always applicable. 74 static if (N >= 1) 75 T width() pure const nothrow 76 { 77 return max.x - min.x; 78 } 79 80 /// Returns: Height of the box, if applicable. 81 static if (N >= 2) 82 T height() pure const nothrow 83 { 84 return max.y - min.y; 85 } 86 87 /// Returns: Depth of the box, if applicable. 88 static if (N >= 3) 89 T depth() pure const nothrow 90 { 91 return max.z - min.z; 92 } 93 94 /// Returns: Signed volume of the box. 95 T volume() pure const nothrow 96 { 97 T res = 1; 98 Bound size = size(); 99 foreach(i; 0 .. N) { res *= size.vector[i]; } 100 return res; 101 } 102 103 /// Returns: true if it contains point. 104 bool contains(Bound point) pure const nothrow 105 { 106 foreach(i; 0 .. N) 107 { 108 if(!(point.vector[i] >= min.vector[i] && point.vector[i] < max.vector[i])) 109 { 110 return false; 111 } 112 } 113 114 return true; 115 } 116 117 /// Returns: true if it contains box other. 118 bool contains(Box other) pure const nothrow 119 { 120 assert(isSorted()); 121 assert(other.isSorted()); 122 123 foreach(i; 0 .. N) 124 { 125 if(other.min.vector[i] >= max.vector[i] || other.max.vector[i] < min.vector[i]) 126 { 127 return false; 128 } 129 } 130 return true; 131 } 132 133 /// Euclidean squared distance from a point. 134 /// See_also: Numerical Recipes Third Edition (2007) 135 double squaredDistance(U)(Vector!(U, N) point) pure const nothrow 136 { 137 double distanceSquared = 0; 138 foreach(i; 0 .. N) 139 { 140 if(point.vector[i] < min.vector[i]) 141 { 142 distanceSquared += (point.vector[i] - min.vector[i]) ^^ 2; 143 } 144 if(point.vector[i] > max.vector[i]) 145 { 146 distanceSquared += (point.vector[i] - max.vector[i]) ^^ 2; 147 } 148 } 149 return distanceSquared; 150 } 151 152 /// Euclidean distance from a point. 153 /// See_also: squaredDistance. 154 double distance(U)(Vector!(U, N) point) 155 { 156 return sqrt(squaredDistance(point)); 157 } 158 159 static if (N == 2u) 160 Box intersect(ref const(Box) o) pure const nothrow 161 { 162 assert(isSorted()); 163 assert(o.isSorted()); 164 auto xmin = .max(min.x, o.min.x); 165 auto ymin = .max(min.y, o.min.y); 166 auto xmax = .min(max.x, o.max.x); 167 auto ymax = .min(max.y, o.max.y); 168 return Box(xmin, ymin, xmax, ymax); 169 } 170 171 172 /// Extends the area of this Box. 173 Box grow(Bound space) pure const nothrow 174 { 175 Box res = this; 176 res.min -= space; 177 res.max += space; 178 return res; 179 } 180 181 /// Shrink the area of this Box. 182 Box shrink(Bound space) pure const nothrow 183 { 184 return grow(-space); 185 } 186 187 /// Extends the area of this Box. 188 Box grow(T space) pure const nothrow 189 { 190 return grow(Bound(space)); 191 } 192 193 /// Shrink the area of this Box. 194 Box shrink(T space) pure const nothrow 195 { 196 return shrink(Bound(space)); 197 } 198 199 /// Assign with another box. 200 ref Box opAssign(U)(U x) nothrow if (is(typeof(x.isBox))) 201 { 202 static if(is(U.element_t : T)) 203 { 204 static if(U._size == _size) 205 { 206 min = x.min; 207 max = x.max; 208 } 209 else 210 { 211 static assert(false, "no conversion between boxes with different dimensions"); 212 } 213 } 214 else 215 { 216 static assert(false, Format!("no conversion from %s to %s", U.element_t.stringof, element_t.stringof)); 217 } 218 return this; 219 } 220 221 /// Returns: true if comparing equal boxes. 222 bool opEquals(U)(U other) pure const nothrow if (is(U : Box)) 223 { 224 return (min == other.min) && (max == other.max); 225 } 226 227 /// Comparison with another Box (for sorting). 228 int opCmp(ref const Box rhs) @safe pure nothrow const @nogc 229 { 230 const cmpMin = min.opCmp(rhs.min); 231 if(cmpMin != 0) { return cmpMin; } 232 return max.opCmp(rhs.max); 233 } 234 235 private: 236 enum isBox = true; 237 enum _size = N; 238 alias T element_t; 239 240 /// Returns: true if each dimension of the box is >= 0. 241 bool isSorted() pure const nothrow 242 { 243 foreach(i; 0 .. N) if(min.vector[i] > max.vector[i]) 244 { 245 return false; 246 } 247 return true; 248 } 249 } 250 251 alias box2i = Box!(int, 2); 252 alias box3i = Box!(int, 3); 253 alias box2 = Box!(float, 2); 254 alias box3 = Box!(float, 3); 255 alias box2d = Box!(double, 2); 256 alias box3d = Box!(double, 3); 257 unittest 258 { 259 box2i a = box2i(1, 2, 3, 4); 260 assert(a.width == 2); 261 assert(a.height == 2); 262 assert(a.volume == 4); 263 box2i b = box2i(vec2i(1, 2), vec2i(3, 4)); 264 assert(a == b); 265 box2i c = box2i(0, 0, 1,1); 266 assert(c.contains(vec2i(0, 0))); 267 assert(!c.contains(vec2i(1, 1))); 268 assert(b.contains(b)); 269 }