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 /// min is inside the box. 24 /// max is just outside. 25 this(bound_t min_, bound_t max_) pure nothrow 26 { 27 min = min_; 28 max = max_; 29 } 30 31 static if (N == 1u) 32 { 33 this(T min_, T max_) pure nothrow 34 { 35 min.x = min_; 36 max.x = max_; 37 } 38 } 39 40 static if (N == 2u) 41 { 42 this(T min_x, T min_y, T max_x, T max_y) pure nothrow 43 { 44 min = bound_t(min_x, min_y); 45 max = bound_t(max_x, max_y); 46 } 47 } 48 49 static if (N == 3u) 50 { 51 this(T min_x, T min_y, T min_z, T max_x, T max_y, T max_z) pure nothrow 52 { 53 min = bound_t(min_x, min_y, min_z); 54 max = bound_t(max_x, max_y, max_z); 55 } 56 } 57 58 59 @property 60 { 61 /// Returns: Dimensions of the box. 62 bound_t size() pure const nothrow 63 { 64 return max - min; 65 } 66 67 /// Returns: Center of the box. 68 bound_t center() pure const nothrow 69 { 70 return (min + max) / 2; 71 } 72 73 /// Returns: Box width of the box. 74 static if (N >= 1) 75 T width() pure const nothrow @property 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 @property 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 @property 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_t size = size(); 99 for(size_t i = 0; i < N; ++i) 100 res *= size[i]; 101 return res; 102 } 103 } 104 105 /// Returns: true if it contains point. 106 bool contains(bound_t point) pure const nothrow 107 { 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 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 130 { 131 double distanceSquared = 0; 132 for (size_t i = 0; i < N; ++i) 133 { 134 if (point[i] < min[i]) 135 distanceSquared += (point[i] - min[i]) ^^ 2; 136 137 if (point[i] > max[i]) 138 distanceSquared += (point[i] - max[i]) ^^ 2; 139 } 140 return distanceSquared; 141 } 142 143 /// Euclidean distance from a point. 144 /// See_also: squaredDistance. 145 double distance(bound_t point) 146 { 147 return sqrt(squaredDistance(point)); 148 } 149 150 static if (N == 2u) 151 { 152 Box intersect(ref const(Box) o) pure const nothrow 153 { 154 assert(isSorted()); 155 assert(o.isSorted()); 156 auto xmin = .max(min.x, o.min.x); 157 auto ymin = .max(min.y, o.min.y); 158 auto xmax = .min(max.x, o.max.x); 159 auto ymax = .min(max.y, o.max.y); 160 return Box(xmin, ymin, xmax, ymax); 161 } 162 } 163 164 165 /// Extends the area of this Box. 166 Box grow(bound_t space) pure const nothrow 167 { 168 Box res = this; 169 res.min -= space; 170 res.max += space; 171 return res; 172 } 173 174 /// Shrink the area of this Box. 175 Box shrink(bound_t space) pure const nothrow 176 { 177 return grow(-space); 178 } 179 180 /// Extends the area of this Box. 181 Box grow(T space) pure const nothrow 182 { 183 return grow(bound_t(space)); 184 } 185 186 /// Shrink the area of this Box. 187 Box shrink(T space) pure const nothrow 188 { 189 return shrink(bound_t(space)); 190 } 191 192 /// Returns: true if each dimension of the box is >= 0. 193 bool isSorted() pure const nothrow 194 { 195 for(size_t i = 0; i < N; ++i) 196 { 197 if (min[i] > max[i]) 198 return false; 199 } 200 return true; 201 } 202 203 /// Assign with another box. 204 ref Box opAssign(U)(U x) nothrow if (is(typeof(x.isBox))) 205 { 206 static if(is(U.element_t : T)) 207 { 208 static if(U._size == _size) 209 { 210 min = x.min; 211 max = x.max; 212 } 213 else 214 { 215 static assert(false, "no conversion between boxes with different dimensions"); 216 } 217 } 218 else 219 { 220 static assert(false, Format!("no conversion from %s to %s", U.element_t.stringof, element_t.stringof)); 221 } 222 return this; 223 } 224 225 /// Returns: true if comparing equal boxes. 226 bool opEquals(U)(U other) pure const nothrow if (is(U : Box)) 227 { 228 return (min == other.min) && (max == other.max); 229 } 230 } 231 232 private 233 { 234 enum isBox = true; 235 enum _size = N; 236 alias T element_t; 237 } 238 } 239 240 /// Instanciate to use a 2D box. 241 template box2(T) 242 { 243 alias Box!(T, 2u) box2; 244 } 245 246 /// Instanciate to use a 3D box. 247 template box3(T) 248 { 249 alias Box!(T, 3u) box3; 250 } 251 252 253 alias box2!int box2i; /// 2D box with integer coordinates. 254 alias box3!int box3i; /// 3D box with integer coordinates. 255 alias box2!float box2f; /// 2D box with float coordinates. 256 alias box3!float box3f; /// 3D box with float coordinates. 257 alias box2!double box2d; /// 2D box with double coordinates. 258 alias box3!double box3d; /// 3D box with double coordinates. 259 260 unittest 261 { 262 box2i a = box2i(1, 2, 3, 4); 263 assert(a.width == 2); 264 assert(a.height == 2); 265 assert(a.volume == 4); 266 box2i b = box2i(vec2i(1, 2), vec2i(3, 4)); 267 assert(a == b); 268 box2i c = box2i(0, 0, 1,1); 269 assert(c.contains(vec2i(0, 0))); 270 assert(!c.contains(vec2i(1, 1))); 271 assert(b.contains(b)); 272 }